Fonctions de conversion explicites, initialisation directe et constructeurs de conversion

Le projet postérieur à la norme n3376 a pour exemple (12.3.2: 2) l’utilisation d’une fonction de conversion explicite en un type défini par l’utilisateur:

class Y { }; struct Z { explicit operator Y() const; }; void h(Z z) { Y y1(z); // OK: direct-initialization } 

D’après 12.3.2: 2, une fonction de conversion explicite est ” uniquement considérée comme une conversion définie par l’utilisateur pour une initialisation directe “; cependant, cela semblerait permettre:

 struct Y { Y(int); }; struct Z { explicit operator int() const; }; void h(Z z) { Y y1(z); // direct-initialization } 

qui semble en contradiction avec l’intention de la norme et qui est en fait rejeté par gcc-4.7.1:

 source.cpp: In function 'void h(Z)': source.cpp:4:9: error: no matching function for call to 'Y::Y(Z&)' source.cpp:4:9: note: candidates are: source.cpp:1:12: note: Y::Y(int) source.cpp:1:12: note: no known conversion for argument 1 from 'Z' to 'int' source.cpp:1:8: note: constexpr Y::Y(const Y&) source.cpp:1:8: note: no known conversion for argument 1 from 'Z' to 'const Y&' source.cpp:1:8: note: constexpr Y::Y(Y&&) source.cpp:1:8: note: no known conversion for argument 1 from 'Z' to 'Y&&' 

Est-ce que gcc a le droit de rejeter la conversion de Z en Y via int , ou est-ce que le standard autorise effectivement cet usage?

J’ai examiné le contexte de l’ initialisation directe mentionnée; Selon la définition de l’initialisation directe en type de classe dans 8.5: 16, un constructeur est appelé avec comme argument l’argument initializer, qui est donc converti en type de paramètre par une séquence de conversion implicite (13.3.3.1). Etant donné qu’une séquence de conversion implicite est une conversion implicite (4: 3) et modélise ainsi l’initialisation par copie (8.5: 14) et non l’initialisation directe, le langage de 12.3.2: 2 doit faire référence à l’expression dans son ensemble.

Notez également qu’il ne s’agit pas d’une violation de 12.3: 4 (conversions multiples définies par l’utilisateur); le même compilateur est satisfait du même code sans explicit (tout comme Clang et Comeau):

 struct Y { Y(int); }; struct Z { operator int(); }; void h(Z z) { Y y1(z); // direct-initialization } 

Je pense que Jesse Good a identifié la distinction entre l’ operator Y et l’ operator int dans 13.3.1.4:1, mais il y a un troisième cas qui me préoccupe toujours:

 struct X {}; struct Y { Y(const X &); }; struct Z { explicit operator X() const; }; void h(Z z) { Y y1(z); // direct-initialization via class-type X } 

L’initialisation du X temporaire devant être lié au seul paramètre const X & du constructeur de Y poursuit dans un contexte d’initialisation directe selon 13.3.1.4:1, avec T tant que X et S tant que Z Je pense que cet article est incorrect et devrait se lire comme suit:

13.3.1.4 Initialisation par copie de la classe par conversion définie par l’utilisateur [over.match.copy]

1 – […] Lors de l’initialisation d’un agent temporaire lié au premier paramètre d’un constructeur, prenant comme premier argument une référence à T éventuellement qualifié de cv , appelé avec un seul argument dans le contexte de l’initialisation directe d’un object. object de type “cv2 T , les fonctions de conversion explicites sont également sockets en compte. […]

Pour éviter toute confusion, je pense que 12.3.2: 2 devrait également être modifié:

12.3.2 Fonctions de conversion [class.conv.fct]

2 – Une fonction de conversion peut être explicite (7.1.2), auquel cas elle n’est considérée comme une conversion définie par l’utilisateur pour une initialisation directe (8.5) que dans certains contextes (13.3.1.4, 13.3.1.5, 13.3.1.6). . […]

Des commentaires sur ce qui précède?

Comme indiqué dans la réponse de Luc Danton, la conversion implicite est définie en termes d’initialisation de copie. Ensuite, si nous regardons 13.3.1.4:1[Copy-initialization de la classe par la conversion définie par l’utilisateur]:

Lorsque le type de l’expression d’initialiseur est un type de classe «cv S», les fonctions de conversion non explicites de S et ses classes de base sont sockets en compte. Lors de l’initialisation temporaire d’un élément à associer au premier paramètre d’un constructeur prenant une référence à un éventuel T qualifié de cv, appelé avec un seul argument dans le contexte d’initialisation directe, les fonctions de conversion explicites sont également sockets en compte. Celles qui ne sont pas cachées dans S et donnent un type dont la version cv-non qualifiée est du même type que T ou est une classe dérivée de celles-ci sont des fonctions candidates . Les fonctions de conversion qui renvoient une «référence à X» renvoient des lvalues ​​ou des xvalues, de type X, en fonction du type de référence, et sont donc considérées comme donnant un X pour ce processus de sélection des fonctions candidates.

Si je comprends bien, le premier fonctionne parce que la fonction de conversion produit un Y et est donc une fonction candidate comme l’indique la deuxième partie soulignée de la citation. Toutefois, dans votre second cas, l’ensemble des fonctions candidates est vide car il n’existe pas de fonction de conversion en Y ni de fonction de conversion non explicite comme l’indique la première partie soulignée.

Concernant le troisième cas:

Après avoir trouvé le rapport de défaut 1087 , il semble clair que l’objective était d’autoriser, de copier, de déplacer et de modéliser des constructeurs lors de l’initialisation directe d’un object de cv2 T comme vous le mentionnez. Si vous lisez le premier passage de 13.3.1.4, il est Assuming that “cv1 T” is the type of the object being initialized, with T a class type , donc je pense que cela implique of an object of type "cv2 T" que vous mention. Cependant, (après l’avoir relu), il semble que le changement dû au rapport de défaut rende le libellé vague et ne couvre pas le troisième cas que vous proposez.

Selon 8.5 et 13.3.1.3, les constructeurs de Y sont pris en compte et le meilleur est sélectionné via la résolution de surcharge. Dans ce cas, les constructeurs pertinents sont Y(int); et les constructeurs copier et déplacer. En cours de résolution de surcharge 13.3.2 Fonctions viables [over.match.viable] spécifie ceci:

3 Deuxièmement, pour que F soit une fonction viable, il doit exister pour chaque argument une séquence de conversion implicite (13.3.3.1) qui convertit cet argument en paramètre correspondant de F […]

Pour tous ces constructeurs, la conversion de Z en int ou en un des arômes de Y n’existe pas. Pour nous convaincre, examinons ce que la norme dit à propos des séquences de conversion implicites dans 13.3.3.1 Séquences de conversion implicites [over.best.ics]:

1 Une séquence de conversion implicite est une séquence de conversions utilisée pour convertir un argument dans un appel de fonction au type du paramètre correspondant à la fonction appelée. La séquence de conversions est une conversion implicite telle que définie à l’Article 4, ce qui signifie qu’elle est régie par les règles d’initialisation d’un object ou d’une référence par une seule expression (8.5, 8.5.3).

Si nous faisons référence à la clause 4, nous apprenons qu’une conversion implicite est définie en termes d’initialisation de copie (c’est-à-dire T t=e;T est int et e est z ):

(§4.3) Une expression e peut être implicitement convertie en un type T si et seulement si la déclaration T t=e; est bien formé, pour certains inventé variable temporaire t (8.5). […]

Je prends donc 12.3.2: 2 de ne pas appliquer cette initialisation, ce qui se passe dans le contexte plus large d’une initialisation directe. Faire autrement serait en contradiction avec ce dernier paragraphe.

Je ne suis pas juriste des langues. Cependant, le libellé de la norme m’implique que pour marquer un opérateur de conversion comme étant explicit , vous devez spécifier explicitement le type de conversion (c’est-à-dire int ) dans le cadre de l’initialisation de l’object y1 . Avec le code Y y1(z) , il semblerait que vous comptiez sur une conversion implicite, car le type que vous spécifiez pour la variable y1 est Y

Par conséquent, je m’attendrais à ce que l’utilisation correcte de l’opérateur de conversion explicite dans cette situation soit:

 Y y1( int(z) ); 

Ou, puisque vous spécifiez effectivement un casting, de préférence

 Y y1( static_cast (z) );