35 votes

Pourquoi cette ambiguïté ?

Considérons que j'ai le code minimal suivant :

#include <boost/type_traits.hpp>

template<typename ptr_t>
struct TData
{
    typedef typename boost::remove_extent<ptr_t>::type value_type;
    ptr_t data;

    value_type & operator [] ( size_t id ) { return data[id]; }
    operator ptr_t & () { return data; }
};

int main( int argc, char ** argv )
{
    TData<float[100][100]> t;   
    t[1][1] = 5;
    return 0;
}

GNU C++ me donne l'erreur :

test.cpp: In function 'int main(int, char**)':
test.cpp:16: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for second:
test.cpp:9: note: candidate 1: typename boost::remove_extent<ptr_t>::type& TData<ptr_t>::operator[](size_t) [with ptr_t = float [100][100]]
test.cpp:16: note: candidate 2: operator[](float (*)[100], int) <built-in>

Mes questions sont les suivantes :

  1. Pourquoi GNU C++ donne l'erreur, mais pas le compilateur Intel C++ ?
  2. Pourquoi changer operator[] à ce qui suit conduit à une compilation sans erreurs ?

    value_type & operator [] ( int id ) { return data[id]; }

Les liens vers la norme C++ sont appréciés.


Comme je peux le voir, il y a deux voies de conversion :

  1. (1) int a size_t et (2) operator[](size_t) .
  2. (1) operator ptr_t&() , (2) int a size_t et (3)intégrer operator[](size_t) .

33voto

Johannes Schaub - litb Points 256113

C'est en fait assez simple. Pour t[1] la résolution des surcharges a ces candidats :

Candidat 1 (intégré : 13.6/13) (T étant un type d'objet arbitraire) :

  • Liste des paramètres : (T*, ptrdiff_t)

Candidat 2 (votre opérateur)

  • Liste des paramètres : (TData<float[100][100]>&, something unsigned)

La liste des arguments est donnée par 13.3.1.2/6 :

L'ensemble des fonctions candidates pour la résolution des surcharges est l'union des candidats membres, des candidats non membres et des candidats intégrés. La liste des arguments contient tous les opérandes de l'opérateur.

  • Liste d'arguments : (TData<float[100][100]>, int)

Vous voyez que le premier argument correspond exactement au premier paramètre du candidat 2. Mais il faut une conversion définie par l'utilisateur pour le premier paramètre du candidat 1. Donc, pour le premier paramètre, le second candidat l'emporte.

Vous voyez également que le résultat de la deuxième position dépend. Faisons quelques hypothèses et voyons ce que nous obtenons :

  1. ptrdiff_t es int : Le premier candidat l'emporte, car il a une correspondance exacte, tandis que le second candidat nécessite une conversion intégrale.
  2. ptrdiff_t es long : Aucun des deux candidats ne gagne, car les deux requièrent une conversion intégrale.

Maintenant, 13.3.3/1 dit

Soit ICSi(F) la séquence de conversion implicite qui convertit le i-ième argument de la liste au type du i-ième paramètre de la fonction viable F.

Une fonction viable F1 est définie comme étant une meilleure fonction qu'une autre fonction viable F2 si, pour tous les arguments i, ICSi(F1) n'est pas une pire séquence de conversion que ICSi(F2), et si ... pour un certain argument j, ICSj(F1) est une meilleure séquence de conversion que ICSj(F2), ou, si ce n'est pas cela ...

Pour notre première hypothèse, nous n'obtenons pas de gagnant global, car le candidat 2 l'emporte pour le premier paramètre, et le candidat 1 pour le second. Je l'appelle le entrecroisé . Pour notre deuxième hypothèse, le candidat 2 l'emporte globalement, car aucun des deux paramètres n'avait une conversion plus mauvaise, mais le premier paramètre avait une conversion plus mauvaise. meilleur conversion.

Pour la première hypothèse, il importe peu que la conversion intégrale (int en unsigned) dans le deuxième paramètre soit un moindre mal que la conversion définie par l'utilisateur de l'autre candidat dans le premier paramètre. Dans le croisement, les règles sont grossières.


Ce dernier point peut encore vous déconcerter, en raison de tout ce qui se passe autour, alors prenons un exemple

void f(int, int) { }
void f(long, char) { }

int main() { f(0, 'a'); }

Cela vous donne le même avertissement GCC déroutant (qui, je m'en souviens, m'a vraiment dérouté lorsque je l'ai reçu pour la première fois il y a quelques années), car 0 se convertit en long pire que 'a' a int - Pourtant, vous obtenez une ambiguïté, car vous êtes dans une situation de croisement.

13voto

Avec l'expression :

t[1][1] = 5;

Le compilateur doit se concentrer sur le côté gauche pour déterminer ce qui va là, donc l'élément = 5; est ignorée jusqu'à ce que la lhs soit résolue. Ce qui nous laisse l'expression : t[1][1] qui représente deux opérations, la seconde opérant sur le résultat de la première, le compilateur ne doit donc prendre en compte que la première partie de l'expression : t[1] Le type réel est (TData&)[(int)]

L'appel ne correspond exactement à aucune fonction, car operator[] pour TData est définie comme la prise d'un size_t donc pour pouvoir l'utiliser, le compilateur devrait convertir l'argument 1 de int a size_t avec une conversion implicite. C'est le premier choix. Maintenant, un autre chemin possible est d'appliquer une conversion définie par l'utilisateur pour convertir TData<float[100][100]> en float[100][100] .

El _int a size_t_ La conversion est une conversion intégrale et est classée comme Conversion dans le tableau 9 de la norme, tout comme la conversion définie par l'utilisateur de TData<float[100][100]> a float[100][100] conversion selon §13.3.3.1.2/4. La conversion de float [100][100]& a float (*)[100] est classée comme Correspondance exacte dans le tableau 9. Le compilateur n'est pas autorisé à choisir entre ces deux séquences de conversion.

Q1 : Tous les compilateurs n'adhèrent pas à la norme de la même manière. Il est assez fréquent de constater que dans certains cas spécifiques, un compilateur se comporte différemment des autres. Dans ce cas, les implémenteurs de g++ ont décidé de se plaindre du fait que la norme ne permet pas au compilateur de choisir, alors que les implémenteurs d'Intel ont probablement simplement appliqué silencieusement leur conversion préférée.

Q2 : Lorsque vous changez la signature de l'utilisateur défini operator[] l'argument correspond exactement au type passé. t[1] correspond parfaitement à t.operator[](1) sans aucune conversion, le compilateur doit donc suivre cette voie.

0voto

Chubsdad Points 14310

J'ai essayé de montrer les deux candidats pour l'expression t[1][1]. Elles sont toutes deux de même RANG (CONVERSION). D'où l'ambiguïté

Je pense que le problème est que l'opérateur intégré [], conformément à la norme 13.6/13, est défini comme suit

T& operator[](T*, ptrdiff_t);

Sur mon système, ptrdiff_t est défini comme 'int' (cela explique-t-il le comportement des x64 ?).

template<typename ptr_t> 
struct TData 
{ 
    typedef typename boost::remove_extent<ptr_t>::type value_type; 
    ptr_t data; 

    value_type & operator [] ( size_t id ) { return data[id]; } 
    operator ptr_t & () { return data; } 
}; 

typedef float (&ATYPE) [100][100];

int main( int argc, char ** argv ) 
{ 
    TData<float[100][100]> t;    

    t[size_t(1)][size_t(1)] = 5; // note the cast. This works now. No ambiguity as operator[] is preferred over built-in operator

    t[1][1] = 5;                 // error, as per the logic given below for Candidate 1 and Candidate 2

    // Candidate 1 (CONVERSION rank)
    // User defined conversion from 'TData' to float array
    (t.operator[](1))[1] = 5;

    // Candidate 2 (CONVERSION rank)
    // User defined conversion from 'TData' to ATYPE
    (t.operator ATYPE())[1][1] = 6;

    return 0; 
}

EDIT :

Voici ce que je pense :

Pour le candidat 1 (opérateur []), la séquence de conversion S1 est la suivante Conversion définie par l'utilisateur - Conversion standard (int vers size_t)

Pour le candidat 2, la séquence de conversion S2 est Conversion définie par l'utilisateur -> int à ptrdiff_t (pour le premier argument) -> int à ptrdiff_t (pour le second argument)

La séquence de conversion S1 est un sous-ensemble de S2 et est censée être meilleure. Mais voici le hic...

La citation de Standard ci-dessous devrait vous aider.

13.3.3.2/3 états - Standard S1 est une meilleure séquence de conversion séquence de conversion que la séquence de séquence de conversion standard S2 si - S1 est une sous-séquence appropriée de S2 (en comparant les séquences de conversion dans la forme canonique définie par 13.3.3.1.1, en excluant toute transformation de Lvalue ; la séquence de conversion d'identité est considérée comme une sous-séquence de toute séquence de conversion non identitaire) ou, si ce n'est pas cela...

L'article 13.3.3.2 stipule : " La séquence de conversion U1, définie par l'utilisateur, est la meilleure solution. U1 est une meilleure séquence de séquence de conversion qu'une autre séquence de conversion définie par l'utilisateur U2 si elles contiennent la même fonction de conversion fonction de conversion ou constructeur défini par l'utilisateur et si la deuxième séquence de conversion standard de U1 est meilleure que la deuxième séquence de conversion standard de U2."

Voici la première partie de la condition et " s'ils contiennent la même fonction de conversion ou le même constructeur défini par l'utilisateur. " ne tient pas la route. Donc, même si la deuxième partie de la condition et " si la deuxième séquence de conversion standard de U1 est meilleure que la deuxième séquence de conversion standard de U2. " tient bon, ni S1 ni S2 n'est préféré à l'autre.

C'est pourquoi la fonction de gcc fantôme message d'erreur "L'ISO C++ dit qu'elles sont ambiguës, même si la pire conversion pour la première est meilleure que la pire conversion pour la seconde".

Ceci explique très bien l'ambiguïté, à mon avis.

0voto

adf88 Points 2347

Je ne sais pas quelle est la réponse exacte, mais...

A cause de cet opérateur :

operator ptr_t & () { return data; }

il existe déjà des systèmes intégrés [] (abonnement à un tableau) qui accepte size_t comme indice. Nous avons donc deux [] opérateurs, les intégrés et définis par vous. Booth accepte size_t donc cela est considéré comme une surcharge illégale probablement.

//EDIT
cela devrait fonctionner comme vous l'aviez prévu

template<typename ptr_t>
struct TData
{
    ptr_t data;
    operator ptr_t & () { return data; }
};

0voto

UncleBens Points 24580

Il me semble qu'avec

t[1][1] = 5;

le compilateur doit choisir entre les deux.

value_type & operator [] ( size_t id ) { return data[id]; }

qui correspondrait si le int littéraux devaient être convertis en size_t ou

operator ptr_t & () { return data; }

suivi d'une indexation normale du tableau, auquel cas le type de l'index correspond exactement.


En ce qui concerne l'erreur, il semble que GCC, en tant qu'extension du compilateur, voudrait choisir la première surcharge pour vous, et vous compilez avec le drapeau -pedantic et/ou -Werror qui le force à s'en tenir au mot de la norme.

(Je ne suis pas d'humeur -pédagogique, donc pas de citations de la norme, surtout sur ce sujet).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X