Considérez 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:
operator[]
par ce qui suit conduit à une compilation sans erreur?
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 chemins de conversion:
int
à size_t
et (2) operator[](size_t)
.operator ptr_t&()
, (2) int
à size_t
et (3) construit operator[](size_t)
.C'est en fait assez simple. Pour t[1]
, la résolution de surcharge a ces candidats:
Candidat 1 (intégré: 13.6/13) (T étant un type d'objet arbitraire):
(T*, ptrdiff_t)
Candidat 2 (votre opérateur)
(TData<float[100][100]>&, something unsigned)
La liste d'arguments est donnée par 13.3.1.2/6
:
L'ensemble des fonctions du candidat pour la résolution de la surcharge est constitué par l'union des candidats membres, des candidats non membres et des candidats intégrés. La liste d'arguments contient tous les opérandes de l'opérateur.
(TData<float[100][100]>, int)
Vous voyez que le premier argument correspond exactement au premier paramètre de Candidate 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 gagne.
Vous voyez également que le résultat de la deuxième position dépend. Faisons quelques hypothèses et voyons ce que nous obtenons:
ptrdiff_t
est int
: le premier candidat gagne parce qu'il correspond exactement, alors que le second candidat nécessite une conversion intégrale.ptrdiff_t
est long
: aucun des candidats ne gagne, car les deux nécessitent une conversion intégrale.Maintenant, 13.3.3/1
dit
Soit ICSi (F) la séquence de conversion implicite qui convertit le i-ème argument de la liste en type du 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 séquence de conversion plus mauvaise que ICSi (F2), puis ... pour un argument j, ICSj ( F1) est une meilleure séquence de conversion que ICSj (F2), ou, sinon, cela ...
Pour notre première hypothèse, nous n'obtenons pas de gagnant global, car le candidat 2 gagne pour le premier paramètre et le candidat 1 gagne pour le deuxième paramètre. Je l'appelle le entrecroisé . Pour notre deuxième hypothèse, le candidat 2 gagne globalement, car aucun des paramètres n’a eu une conversion plus mauvaise, mais le premier paramètre a eu une conversion meilleure.
Pour la première hypothèse, peu importe que la conversion intégrale (int en non signé) dans le deuxième paramètre soit moins dommageable que la conversion définie par l'utilisateur de l'autre candidat dans le premier paramètre. Dans le jeu croisé, les règles sont grossières.
Ce dernier point pourrait encore vous dérouter, à cause de toute cette histoire, alors prenons un exemple.
void f(int, int) { }
void f(long, char) { }
int main() { f(0, 'a'); }
Cela vous donne le même avertissement déroutant de GCC (ce qui, je me souviens, déroutait en fait lorsque je l’ai reçu pour la première fois il ya quelques années), parce que 0
se transforme en long
pire que 'a'
en int
- mais vous obtenez une ambiguïté, car vous êtes dans une situation entrecroisée.
Avec l'expression:
t[1][1] = 5;
Le compilateur doit se concentrer sur le côté gauche pour déterminer ce qui s'y passe, donc le = 5;
est ignoré jusqu'à ce que le lhs soit résolu. En nous laissant avec l'expression: t[1][1]
, qui représente deux opérations, la seconde opérant sur le résultat du premier, le compilateur ne doit donc prendre en compte que la première partie de l'expression: t[1]
. Le type actuel est (TData&)[(int)]
.
L'appel ne correspond pas exactement à une fonction, car operator[]
pour TData
est défini comme prenant un argument size_t
. Par conséquent, pour pouvoir l'utiliser, le compilateur devrait convertir 1
de int
à size_t
avec une conversion implicite. C'est le premier choix. Maintenant, un autre chemin possible consiste à appliquer une conversion définie par l'utilisateur pour convertir TData<float[100][100]>
en float[100][100]
.
Leint
TO size_t
conversion est un conversion intégrale et est classé comme Conversion dans le tableau 9 de la norme, de même que le conversion définie par l'utilisateur deTData<float[100][100]>
&AGRAVE; float[100][100]
conversion conformément au §13.3.3.1.2/4. La conversion de float [100][100]&
en float (*)[100]
est classée comme Correspondance exacte dans le tableau 9. Le compilateur n'est pas autorisé à choisir parmi 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 courant de découvrir que dans certains cas spécifiques, un compilateur fonctionnera différemment des autres. Dans ce cas, les développeurs g ++ ont décidé de se plaindre de la norme ne permettant pas au compilateur de choisir, tandis que les développeurs Intel ont probablement appliqué leur conversion préférée en mode silencieux.
Q2 : Lorsque vous modifiez la signature du operator[]
défini par l'utilisateur, l'argument correspond exactement au type passé. t[1]
est une correspondance parfaite pour t.operator[](1)
sans aucune conversion, le compilateur doit donc suivre ce chemin.
J'ai essayé de montrer les deux candidats pour l'expression t [1] [1]. Ce sont tous deux égaux RANK (CONVERSION). D'où l'ambiguïté
Je pense que le problème est que l’opérateur intégré [] conformément à 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 de 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;
}
MODIFIER:
Voici ce que je pense:
Pour le candidat 1 (opérateur []), la séquence de conversion S1 est une conversion définie par l'utilisateur - Conversion standard (int en taille_t)
Pour le candidat 2, la séquence de conversion S2 est Conversion définie par l'utilisateur -> int en ptrdiff_t (pour le premier argument) -> int en ptrdiff_t (pour le deuxième argument).
La séquence de conversion S1 est un sous-ensemble de S2 et est supposée être meilleure. Mais voici la prise...
Ici, la citation ci-dessous de Standard devrait aider.
13.3.3.2/3 états - La séquence de conversion standard S1 est une meilleure séquence de conversion que la séquence de conversion standard S2 si - S1 est une sous-séquence appropriée de S2 (en comparant les séquences de conversion sous la forme canonique définie par 13.3.3.1.1, à l'exclusion de toute valeur Lvalue). Transformation: la séquence de conversion d’identité est considérée comme une sous-séquence de toute séquence de conversion autre que d’identité) ou, sinon, ...
La séquence de conversion définie par l'utilisateur U1 est une meilleure séquence de conversion qu'une autre séquence de conversion définie par l'utilisateur U2 si elle contient la même fonction de conversion définie par l'utilisateur ou le même constructeur et si la deuxième séquence de conversion standard de U1 est meilleure que le deuxième séquence de conversion standard de U2. "
Ici, la première partie de la condition et " si elles contiennent la même fonction de conversion définie par l'utilisateur ou le même constructeur " ne tient pas. Ainsi, 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. " n'est pas valable, ni S1 ni S2 ne sont préférés .
C'est pourquoi le message phantom error de gcc "ISO C++ dit que celles-ci sont ambiguës, même si la pire conversion pour la première est meilleure que la pire pour la seconde"
Cela explique l'ambiguïté calme bien mon humble avis
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à un opérateur []
intégré (abonnement à un tableau) qui accepte size_t
en tant qu'index. Nous avons donc deux opérateurs []
, intégré et défini par vous. Booth accepte size_t
, ce qui est probablement considéré comme une surcharge illégale.
//MODIFIER
Cela devrait fonctionner comme prévu
template<typename ptr_t>
struct TData
{
ptr_t data;
operator ptr_t & () { return data; }
};
La résolution de surcharge est un mal de tête. Mais puisque vous êtes tombé sur un correctif (éliminer la conversion de l'opérande d'index en operator[]
) qui est trop spécifique à l'exemple (les littéraux sont de type int
mais la plupart des variables que vous utiliserez ne le sont pas), vous pouvez peut-être le généraliser:
template< typename IT>
typename boost::enable_if< typename boost::is_integral< IT >::type, value_type & >::type
operator [] ( IT id ) { return data[id]; }
Malheureusement, je ne peux pas tester cela car GCC 4.2.1 et 4.5 acceptent votre exemple sans se plaindre sous --pedantic
. Ce qui soulève vraiment la question de savoir s'il s'agit d'un bug du compilateur ou non.
De plus, une fois que j'ai éliminé la dépendance Boost, cela a dépassé Comeau.
Il me semble qu'avec
t[1][1] = 5;
le compilateur doit choisir entre.
value_type & operator [] ( size_t id ) { return data[id]; }
qui correspondrait si le littéral int
était converti en size_t
, ou
operator ptr_t & () { return data; }
suivi de l'indexation normale des tableaux, auquel cas le type de l'index correspond exactement.
En ce qui concerne l'erreur, il semblerait que GCC, en tant qu'extension de compilation, souhaite choisir la première surcharge, et vous compilez avec le drapeau -pedantic et/ou -Werror qui l'oblige à s'en tenir à la Parole du standard.
(Je ne suis pas d'humeur sentimentale, donc pas de citations de la norme, en particulier sur ce sujet.)