1. #1
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    août 2006
    Messages
    227
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : août 2006
    Messages : 227
    Points : 488
    Points
    488

    Par défaut Opérateur non trouvé : problème d'ADL ?

    Bonjour,

    j'ai un problème avec un opérateur récalcitrant ; je soupçonne fortement un problème d'ADL (Argument-dependent lookup) :
    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
    namespace test
    {  
        template<typename L, typename R>
        int operator|(const L & , const R &)
        { return 0; }
     
        template<typename T>
        struct enable
        {};
    }
     
    namespace foo
    {
        struct want_op : test::enable<want_op>
        {  
            enum enum_type
            { inner
            };
        };
     
        enum enum_type
        { outer
        };
    }
     
    int main()
    {
        //using test::operator|;
     
        foo::want_op o;
        o | foo::want_op::inner;
        10         | o;
        foo::outer | o;
     
        foo::want_op::inner | o; 
    }
    Seule la ligne 35 pose souci.

    Le problème existe avec MSVC 2005/8.0 () mais est également reproductible avec la dernière version en ligne (Compiler version: 19.11.25331.0)
    Citation Envoyé par msvc
    main.cpp(35): error C2676: binary '|': 'foo::want_op::enum_type' does not define this operator or a conversion to a type acceptable to the predefined operator
    Le plus étrange est que GCC accepte ce code tandis que Clang bute sur la même ligne :
    Citation Envoyé par clang
    main.cpp:35:25: error: invalid operands to binary expression ('int' and 'foo::want_op') foo::want_op::inner | o;
    Décommenter la ligne using test::operator|; résout le problème pour tous les compilateurs (d'où l'hypothèse d'un problème d'ADL).

    J'aurais pensé qu'il s'agit d'un bug si MSVC et Clang n'était pas d'accord, mais je ne vois pas non plus quelle règle du standard pourrait s'appliquer ici.


    Merci d'avance.

  2. #2
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2007
    Messages
    4 760
    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 : 4 760
    Points : 15 449
    Points
    15 449

    Par défaut

    Lorsqu'un opérateur est trouvé, seules les surcharges définies dans l'espace courant et dans l'espace de chaque opérande sont visibles.
    On peut remonter, ce qui est dans un espace N donné est aussi accessible dans les espaces contenus dans N (comme N::bidule)

    tes appels se trouvent dans main, contenu dans l'espace global.
    les types des arguments des trois appels sont soit dans foo, soit dans l'espace global (pour 10)
    Donc, les surcharges trouvées sont celles dans foo et dans l'espace global.
    Donc, pas dans test, qui ne contient ni foo ni l'espace globale.

    La directive using injectant le nom dans l'espace courant (à s'avoir l'espace global), elle est précisément la solution au problème.

    A présent, la raison pour laquelle seule la ligne 35 échoue, c'est que tes enum ne sont pas des enum class, donc, leurs valeurs sont convertibles en entier, qui fournit un opérator |.

    En l'occurence, la seule ligne à échouer est celle dont les deux arguments sont du même type.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  3. #3
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    août 2006
    Messages
    227
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : août 2006
    Messages : 227
    Points : 488
    Points
    488

    Par défaut

    Citation Envoyé par ternel Voir le message
    Donc, pas dans test, qui ne contient ni foo ni l'espace globale.
    D'après le point 6.4.2.2 de la norme (page 55):
    Citation Envoyé par iso c++
    If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classe
    Les namespaces de classes associées sont également considérées (les classes associées comprenant les classes héritées directement ou indirectement), donc le namespace test devrait être considéré.
    D'ailleurs les lignes 31, 32, 33 compilent grâce à cela.



    Citation Envoyé par ternel Voir le message
    A présent, la raison pour laquelle seule la ligne 35 échoue, c'est que tes enum ne sont pas des enum class, donc, leurs valeurs sont convertibles en entier, qui fournit un opérator |.

    En l'occurence, la seule ligne à échouer est celle dont les deux arguments sont du même type.
    Les arguments ne sont pas identiques ligne 35 ; il s'agit de foo::want_op::enum_type et foo::want_op. Or l'ADL pour foo::want_op et foo::want_op::enum_type (soit les mêmes arguments mais dans un ordre différent !) réussi à la ligne 31.


    Que l'ADL se comporte différemment selon l'ordre des arguments me semble assez surprenant (sauf sous GCC où tout compile).

  4. #4
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2007
    Messages
    4 760
    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 : 4 760
    Points : 15 449
    Points
    15 449

    Par défaut

    Plus je relis le code, moins je comprends le problème, en effet.
    J'ai l'impression qu'il y a une recherche d'operateur membre dans la soupe.

    test::operator| n'est associé à aucune des recherches, car il est dans un namespace trop dense.
    Par contre, comme les enums simples sont des entiers, enum1 | enum2 doit compiler tout court.

    N'ayant pas de compilateur accessible pour le moment, quels sont les messages d'erreur si on supprime complement l'opérateur dans test?
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  5. #5
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    août 2006
    Messages
    227
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : août 2006
    Messages : 227
    Points : 488
    Points
    488

    Par défaut

    Citation Envoyé par ternel Voir le message
    N'ayant pas de compilateur accessible pour le moment, quels sont les messages d'erreur si on supprime complement l'opérateur dans test?
    Sans cet opérateur aucune des lignes ne peut compiler car le type foo::want_op - qui est une structure - y est toujours impliqué.

    Avec les compilateurs (GCC/Clang) présents sur la page cpp/language/adl (-> Coliru).
    Citation Envoyé par GCC
    main.cpp: In function 'int main()': main.cpp:31:7: error: no match for 'operator|' (operand types are 'foo::want_op' and 'foo::want_op::enum_type') o | foo::want_op::inner;
    main.cpp:32:16: error: no match for 'operator|' (operand types are 'int' and 'foo::want_op') 10 | o;
    main.cpp:33:16: error: no match for 'operator|' (operand types are 'foo::enum_type' and 'foo::want_op') foo::outer | o;
    main.cpp:35:25: error: no match for 'operator|' (operand types are 'foo::want_op::enum_type' and 'foo::want_op') foo::want_op::inner | o;
    Citation Envoyé par Clang
    main.cpp:31:7: error: invalid operands to binary expression ('foo::want_op' and 'int') o | foo::want_op::inner;
    main.cpp:32:16: error: invalid operands to binary expression ('int' and 'foo::want_op') 10 | o;
    main.cpp:33:16: error: invalid operands to binary expression ('int' and 'foo::want_op') foo::outer | o;
    main.cpp:35:25: error: invalid operands to binary expression ('int' and 'foo::want_op') foo::want_op::inner | o;
    Et pour MSVC (testé sur http://webcompiler.cloudapp.net/).
    Citation Envoyé par MSVC
    main.cpp(31): error C2676: binary '|': 'foo::want_op' does not define this operator or a conversion to a type acceptable to the predefined operator
    main.cpp(32): error C2677: binary '|': no global operator found which takes type 'foo::want_op' (or there is no acceptable conversion)
    main.cpp(33): error C2676: binary '|': 'foo::enum_type' does not define this operator or a conversion to a type acceptable to the predefined operator
    main.cpp(35): error C2676: binary '|': 'foo::want_op::enum_type' does not define this operator or a conversion to a type acceptable to the predefined operator

  6. #6
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    août 2006
    Messages
    227
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : août 2006
    Messages : 227
    Points : 488
    Points
    488

    Par défaut

    Autre code sans le namespace test
    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
    namespace foo
    {
        struct base
        {
            template<typename L, typename R>
            friend int operator|(const L & , const R &)
            { return 0; }
        };
     
        struct want_op : base
        {  
            enum enum_type
            { inner
            };
        };
     
        enum enum_type
        { outer
        };
    }
     
    int main()
    {
        foo::want_op o;
        o | foo::want_op::inner;
        10         | o;
        foo::outer | o;
     
        foo::want_op::inner | o; 
    }
    Cette fois GCC et MSVC sont content. Seul Clang pose problème et toujours sur la dernière ligne :
    Citation Envoyé par Clang
    main.cpp:29:25: error: invalid operands to binary expression ('int' and 'foo::want_op') foo::want_op::inner | o;
    Mais je crois que la réussite de MSVC ici tient plus à un bug qui en cache un autre .

    En effet, en prenant le code suivant :
    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
    struct not_in_foo
    {};
     
    namespace foo
    {
        struct base
        {
            template<typename L>
            friend int operator|(const L &, const not_in_foo &)
            { return 0; }
        };
     
        struct want_op : base
        {  
            enum enum_type
            { inner
            };
        };
    }
     
    int main()
    {
        not_in_foo nif;	
        foo::want_op::inner | nif;
    }
    Seul MSVC accepte le code ; or ici, je pense que ce ne devrait pas être le cas :
    Citation Envoyé par iso c++
    If T is an enumeration type, its associated namespace is the innermost enclosing namespace of its declaration. If it is a class member, its associated class is the member’s class; else it has no associated class.
    Pour un type énuméré, seule la classe le contenant devrait être associée (donc pas sa classe parent).

    D'ailleurs ce code est bien correctement rejeté par tous les compilateurs :
    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
    struct not_in_foo_or_test
    {};
     
    namespace test
    {  
        template<typename L>
        int operator|(const L & , const not_in_foo_or_test &)
        { return 0; }
     
        template<typename T>
        struct enable
        {};
    }
     
    namespace foo
    {
        struct want_op : test::enable<want_op>
        {  
            enum enum_type
            { inner
            };
        };
    }
     
    int main()
    {
        not_in_foo_or_test nifot;	
        foo::want_op::inner | nifot;
    }
    D'où mon hypothèse sur le bug : lorsque que les compilateurs construisent leurs ensembles de namespaces et classes pour l'ADL, dans le cas du type énuméré want_op::enum_type ils ajoutent seulement la classe want_op, conformément à la norme (sauf MSVC pour le cas avec foo::base). Quand l'argument de type structure want_op est considéré, Clang et MSVC trouvent que celle-ci fait déjà partie de l'ensemble en construction pour l'ADL et n'y ajoute rien de plus, alors qu'il faudrait inclure les classes et namespaces associés (ce que fait GCC).

  7. #7
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2007
    Messages
    4 760
    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 : 4 760
    Points : 15 449
    Points
    15 449

    Par défaut

    Merci pour cette analyse.
    Je vais continuer à me creuser la tête sur le sujet…
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  8. #8
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    août 2006
    Messages
    227
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : août 2006
    Messages : 227
    Points : 488
    Points
    488

    Par défaut

    Avec cette hypothèse, en contournement il est possible d'extraire l'énumération dans une autre structure puis d'en hériter (pour garder la syntaxe imitant les class enum) ; ainsi le type want_op n'est plus considéré par l'ADL pour un argument de type énuméré mais seulement pour un argument de son propre type (qui inclura correctement toutes les classes/namespaces).
    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
    namespace test
    {  
        template<typename L, typename R>
        int operator|(const L & , const R &)
        { return 0; }
     
        template<typename T>
        struct enable
        {};
    }
     
    namespace foo
    {
        struct want_op_values
        {  
            enum enum_type
            { inner
            };
        };
     
        struct want_op : test::enable<want_op>, want_op_values
        {};
    }
     
    int main()
    {
        foo::want_op o;
        o | foo::want_op::inner;   
        foo::want_op::inner | o; 
    }
    Cela semble passer sur tous les compilateurs (mais je ne sais pas si je pourrais l'adapter partout sur le code réel, MSVC n'étant de plus pas toujours très bon sur "l'empty base optimisation" avec de l'héritage multiple et/ou compliqué).

  9. #9
    Membre chevronné
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2011
    Messages
    476
    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 : 476
    Points : 1 996
    Points
    1 996

    Par défaut

    Tu peux utiliser un trait qui autorise ou non la surcharge et un opérateur importé dans le scope global pour casser l'ADL.

    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
    46
    47
    48
    49
    50
    51
    52
    #include <type_traits>
     
    namespace test
    {
      template<class T>
      struct enable    
        : std::false_type
      {};
     
      template<class T> using numeric_candidate = std::integral_constant<bool, std::is_integral<T>::value || std::is_enum<T>::value>;
     
      template<typename L, typename R>
      std::enable_if_t<(enable<L>::value || enable<R>::value) && (numeric_candidate<L>::value ^ numeric_candidate<R>::value), int>
      operator|(const L & , const R &)
      { return 0; }
    }
     
    using test::operator|;
     
    namespace foo
    {
        struct want_op
        {  
            enum enum_type
            { inner
            };
        };
     
        enum enum_type
        { outer
        };
    }
     
    namespace test
    {
      template<>
      struct enable< ::foo::want_op>
        : std::true_type
      {};
    }
     
    int main()
    {
        //using test::operator|;
     
        foo::want_op o;
        o | foo::want_op::inner;
        10         | o;
        foo::outer | o;
     
        foo::want_op::inner | o; 
    }

  10. #10
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    août 2006
    Messages
    227
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : août 2006
    Messages : 227
    Points : 488
    Points
    488

    Par défaut

    Citation Envoyé par jo_link_noir Voir le message
    Tu peux utiliser un trait qui autorise ou non la surcharge et un opérateur importé dans le scope global pour casser l'ADL.
    C'est effectivement une solution.

    Malheureusement dans mon cas réel d'utilisation je l'emploie déjà sur ces opérateurs avec pas mal de SFINAE et de métaprogrammation pour définir lesquels doivent être sélectionnés (enfin dans les limites qui ne font pas disjoncter MSVC 2005/8.0 ).

    D'ailleurs au départ ces opérateurs étaient dans le namespace global, mais leur simple inclusion dans un projet doublait les temps de compilations (devoir tester certains opérateurs trop courant tels que +, - sur tous les types dans toutes les expressions rencontrées prenait trop de temps surtout pour être au final éliminés par SFINAE dans +90% des cas). Avec l'usage de namespaces et d'ADL, je suis revenu aux temps de compilations d'avant leurs inclusions à la seconde près.


    Je devrais tout de même m'en sortir avec une solution de ce type (sans héritage multiple cette fois pour ne pas augmenter la taille des objets sur les compilateurs qui ont du mal avec les "empty base optimisation") :
    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
    namespace test
    {  
        template<typename L, typename R>
        int operator|(const L & , const R &)
        { return 0; }
     
        template<typename T, typename Base = void>
        struct enable : Base 
        {};
     
        template<typename T>
        struct enable<T, void> 
        {};
    }
     
    namespace foo
    {
        struct want_op_values
        {  
            enum enum_type
            { inner
            };
        };
     
        struct want_op : test::enable<want_op, want_op_values> 
        {};
    }
     
    int main()
    {
        foo::want_op o;
        o | foo::want_op::inner;   
        foo::want_op::inner | o; 
    }
    J'ai remonté le point chez Microsoft (avec mon affreux anglais ) : https://connect.microsoft.com/Visual...etails/3136172.

    Je n'ai en revanche pas de compte pour le remonter sur Clang/LLVM (et déjà chez MS avec un compte ça a été une catastrophe avec une dizaine de plantages lors de la soumission et au final le point remonté 4 fois ... ).

    Merci pour toutes vos suggestions.

    Je passe la discussion en .

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

Discussions similaires

  1. Réponses: 9
    Dernier message: 18/02/2008, 20h27
  2. [S2+Tiles2] Problème de définitions non trouvées.
    Par petitpasdelune dans le forum Struts 2
    Réponses: 4
    Dernier message: 26/08/2007, 16h37
  3. [Loader] Problème de classe non trouvée avec LoadFile
    Par Ericx_25 dans le forum Autres composants
    Réponses: 3
    Dernier message: 29/01/2007, 19h01
  4. Problème avec la méthode Buidmenu non trouvée
    Par franckjava dans le forum NetBeans
    Réponses: 2
    Dernier message: 17/01/2007, 00h45
  5. problème de fonction non trouvées
    Par youp_db dans le forum JavaScript
    Réponses: 8
    Dernier message: 27/09/2006, 15h01

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