IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Instanciation d'un template qui devrait être invalide, mais ça passe


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 496
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 496
    Billets dans le blog
    1
    Par défaut Instanciation d'un template qui devrait être invalide, mais ça passe
    Bonjour,

    Voici un code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <iostream>
     
    template<typename T>
    class Wrapper {
    public:
    	Wrapper(T value) :
    			raw_data(value) {
    	}
     
    	explicit operator T() const {
    		return raw_data;
    	}
     
    	friend const Wrapper operator *(const Wrapper& multiplier, const Wrapper& multiplicand) {
    		T raw_result = multiplier.raw_data * multiplicand.raw_data;
    		return Wrapper(raw_result);
    	}
     
    private:
    	T raw_data;
    };
     
    using MyWrapper = Wrapper<int>;
     
    template<typename U>
    int multiply_then_convert(U value) {
    	U multiplied = 2 * value;
    	int converted = multiplied; // WTF? explicit?
    	return converted;
    }
     
    int main() {
    	{
    		// Avec fonction template
    		MyWrapper value = 25;
    		int converted = multiply_then_convert(value);
    	}
     
    	{
    		// Equivalent
    		MyWrapper value = 25;
    		MyWrapper multiplied = 2 * value;
    		int converted = multiplied;
    	}
    }
    La ligne 43 ne compile pas. C'est normal, l'opérateur de conversion est explicite, il faut donc faire un cast.

    La ligne 28 fait la même chose. L'instanciation de cette fonction template pour un tel U (= Wrapper<int>) ne devrait donc pas compiler. Or, la ligne 36 passe comme une lettre à la Poste. L'exécution produit le résultat attendu (50).

    Pourquoi ?

    Merci d'avance pour vos réponses !

  2. #2
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Je confirme qu'avec mon gcc 7, j'ai le même résultat.

    Et la même chose avec la variante suivante de ta template.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<typename U>
    int multiply_then_convert(Wrapper<U> value) {
    	Wrapper<U> multiplied = 2 * value;
    	int converted = multiplied; // WTF? explicit?
    	return converted;
    }
    Je ne comprends pas pourquoi, cependant.

  3. #3
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    La raison est toute simple: c'est toujours le premier paramètre qui prime pour les opérateurs...

    Ainsi, quand tu écris un code proche de MyWrapper multiplied = 2 * value; //où value est de type MyWrapper, si tu laisses (temporairement) la partie gauche de l'affectation de coté, tu te retrouve avec 2*value; et, comme 2 est un littéral, le compilateur pourra choisir entre
    • int operator * (int, int)
    • unsigned int operator * (unsigned int , unsigned int)
    • long operator * (long, long)
    • unsigned long operator * (unsigned long, unsigned long)

    (le tout dépendant du type qu'il va associer à 2 )

    Mais il ne pensera jamais à tenter une conversion dans une optique SFINAE...

    Alors, tu me demanderas sans doute pourquoi MyWrapper = value * 2; //où value est de type MyWrapper pourrait fonctionner

    Hé bien, encore une fois, parce que le premier paramètre prime, et que, vu qu'il a un wrapper<int> comme premier paramètre, il va "logiquement" penser à faire la conversion de 2 en une instance (anonyme temporaire) de Wrapper<int>, vu qu'il saura que c'est... en cela qu'il doit faire la conversion

    Si tu veux que le premier code fonctionne, tu devras fournir un opérateur * dont le premier paramètre puisse être un littéral, sous une forme qui pourrait très bien être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <typename T>
    Wrapper<T> operator * (T litteral, Wrapper<T> const & w){
        return w * litteral; // on utilise l'opérateur qui fonctionne ;)
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #4
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    763
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 763
    Par défaut
    Bah moi j'ai une erreur avec gcc-7.2 et clang-5.

    @koala: ce n'est pas la multiplication qui cause problème, mais le cast implicite vers un int. Ton résonnement est faux aussi, pour les fonctions libres, ce n'est pas le premier paramètre qui prime mais tous les paramètres dans leur ensemble. Mais puisque la conversion implicite vers un int est théoriquement impossible, le prototype operator*(int,int) est exclus et operator*(Wrapper<int>,Wrapper<int>) est utilisé (appel implicite du constructeur de Wrapper).

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par jo_link_noir Voir le message
    Ton résonnement est faux aussi, pour les fonctions libres, ce n'est pas le premier paramètre qui prime mais tous les paramètres dans leur ensemble.
    Pour les fonctions libres, oui, mais, ici, on est dans le cas spécifique d'un opérateur, qui ne prend pas deux paramètres quelconques, mais bien deux opérandes : un qui se trouve à gauche de l'opérateur, et l'autre qui se trouve à droite de l'opérateur.

    Or, dans ce cas particulier, c'est bel et bien l'opérande de gauche (et donc le premier paramètre) qui prime.La preuve, bien que les deux codes soient sensiblement identique, tu obtiendra un résultat différent avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(){
        int i = 3;
        float f= 3.141592;
        auto result = i * f;
        std::cout<<result<<"\n";
    }
    ou avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int main(){
        int i = 3;
        float f= 3.141592;
        auto result = f * i;
        std::cout<<result<<"\n";
    }
    Pourquoi parce que le compilateur effectue une conversion implicite du deuxième paramètre (de l'opérande de droite, pour être précis) pour qu'il corresponde au type du premier (de l'opérande de gauche, donc), et que le type du résultat dépend également de l'opérande de gauche

    Ici, on est exactement dans le même cas: si tu veux pouvoir profiter de la commutativité entre un type primitif et ton type défini par l'utilisateur, tu dois prévoir les deux situations, car le compilateur n'envisagera jamais la conversion de l'opérande de gauche

    EDIT : A la limite, tu prévoirais une fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <typename T>
    Wrapper<T> multiply(Wrapper<T> const & a, Wrapper<T> const & b){
        /* ... */
    }
    là, effectivement, tu devrais pouvoir utiliser deux wrapper, un wrapper et un entier (dans n'importe quel ordre), voir même -- a priori -- deux entiers, car le compilateur envisagera la conversion implicite si elle lui est autorisée (ce qui est le cas ici).

    Mais, c'est valable pour les fonctions libres, pas pour les opérateurs
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    763
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 763
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Pour les fonctions libres, oui, mais, ici, on est dans le cas spécifique d'un opérateur, qui ne prend pas deux paramètres quelconques, mais bien deux opérandes : un qui se trouve à gauche de l'opérateur, et l'autre qui se trouve à droite de l'opérateur.
    Ce qui est identique à un appel de fonction libre, sauf au niveau de la syntaxe.

    Citation Envoyé par koala01 Voir le message
    Or, dans ce cas particulier, c'est bel et bien l'opérande de gauche (et donc le premier paramètre) qui prime.La preuve, bien que les deux codes soient sensiblement identique, tu obtiendra un résultat différent avec [...]
    Non, le résultat est identique. Sur les scalaires, les opérandes sont convertis vers le type pouvant prendre la plus grande valeur entre les 2 opérandes. Donc int+float ou float+int donne toujours un float car l'opérateur utilisé est operator+(float,float).

    Citation Envoyé par koala01 Voir le message
    EDIT : A la limite, tu prévoirais une fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <typename T>
    Wrapper<T> multiply(Wrapper<T> const & a, Wrapper<T> const & b){
        /* ... */
    }
    là, effectivement, tu devrais pouvoir utiliser deux wrapper, un wrapper et un entier (dans n'importe quel ordre), voir même -- a priori -- deux entiers, car le compilateur envisagera la conversion implicite si elle lui est autorisée (ce qui est le cas ici).

    Mais, c'est valable pour les fonctions libres, pas pour les opérateurs
    Si. C'est ce qui d'ailleurs fait dans le code de @Bktero et cela fonctionne sans surprise. Sauf avec 2 entiers bien sûr.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 12
    Dernier message: 03/09/2015, 19h06
  2. Requête HTTP qui devrait être toute simple !
    Par fagma dans le forum GWT et Vaadin
    Réponses: 11
    Dernier message: 27/09/2012, 12h23
  3. hover(), l'objet qui devrait être visible reste caché
    Par moumous24 dans le forum jQuery
    Réponses: 1
    Dernier message: 25/01/2011, 21h26
  4. [PMD] règle PMD non violée, mais qui devrait l'être!
    Par lassto dans le forum Qualimétrie
    Réponses: 0
    Dernier message: 18/05/2010, 10h14
  5. Réponses: 6
    Dernier message: 20/07/2007, 13h16

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo