Short
La fonction de conversion operator int()
est sélectionné (correctement -si je ne me trompe pas-) par clang par rapport à operator bool() const
depuis b
n'est pas qualifié de const, alors que l'opérateur de conversion pour bool l'est.
En résumé, le raisonnement est le suivant : les fonctions candidates à la résolution de surcharge (avec un paramètre objet implicite en place), lors de la conversion de la fonction b
à bool
sont
operator bool (B2 const &);
operator int (B2 &);
où le second correspond mieux puisque b
n'est pas qualifié pour la constance.
Si les deux ont la même qualification, operator bool
est choisi car il permet une conversion directe.
Conversion via la notation des castes, analysée étape par étape
Si nous convenons que l'inserteur ostream booléen (std::basic_ostream::operator<<(bool val) selon [ostream.inserters.arithmetic]) est appelé avec la valeur qui résulte d'une conversion de b
à bool
nous pouvons creuser dans cette conversion.
1. L'expression coulée
La transformation de b en bool
(bool)b
évalue à
static_cast<bool>(b)
selon C++11, 5.4/4 [expr.cast]. depuis const_cast
n'est pas applicable (il ne s'agit pas d'ajouter ou de supprimer des constantes ici).
Cette conversion statique est autorisée par C++11, 5.2.9/4 [expr.static.cast] si bool t(b);
pour une variable inventée t est bien formée. De telles instructions sont appelées initialisation directe selon C++11, 8.5/15 [dcl.init] .
2. Initialisation directe bool t(b);
Clause 16 du paragraphe standard le moins mentionné stipule (c'est moi qui souligne) :
La sémantique des initialisateurs est la suivante. Le type de destination est le type de l'objet ou de la référence en cours d'initialisation et le type de source est le type de l'expression de l'initialisateur.
[...]
[...] si le type de source est un (éventuellement qualifié de cv) type de classe, fonctions de conversion sont prises en compte.
Les fonctions de conversion applicables sont énumérées, et la meilleure est choisie par résolution de surcharge.
2.1 Quelles sont les fonctions de conversion disponibles ?
Les fonctions de conversion disponibles sont operator int ()
y operator bool() const
puisque comme C++11, 12.3/5 [class.conv] nous dit :
Une fonction de conversion dans une classe dérivée ne cache pas une fonction de conversion dans une classe de base, sauf si les deux fonctions convertissent au même type.
Alors que C++11, 13.3.1.5/1 [over.match.conv] (en anglais) États :
Les fonctions de conversion de S et de ses classes de base sont considérées.
où S est la classe qui sera convertie.
2.2 Quelles fonctions de conversion sont applicables ?
C++11, 13.3.1.5/1 [over.match.conv] (en anglais) (c'est moi qui souligne) :
1 [...] En supposant que "cv1 T" est le type de l'objet à initialiser, et "cv S" est le type de l'expression de l'initialisateur, avec S un type de classe, les fonctions candidates sont sélectionnées comme suit : Les fonctions de conversion de S et de ses classes de base sont considérées. Les fonctions de conversion non explicites qui ne sont pas cachées dans S et qui donnent le type T ou un type qui peut être converti en type T par une séquence de conversion standard sont des fonctions candidates.
Par conséquent, operator bool () const
est applicable puisqu'il n'est pas caché dans B2
et donne un bool
.
La partie soulignée dans la dernière citation standard est pertinente pour la conversion à l'aide de operator int ()
depuis int
est un type qui peut être converti en bool via la séquence de conversion standard. La conversion de int
à bool
n'est même pas une séquence mais une simple conversion directe qui est permise par C++11, 4.12/1 [conv.bool].
Une prvalue de type arithmétique, énumération non scopée, pointeur ou pointeur vers un type membre peut être convertie en une prvalue de type bool. Une valeur nulle, une valeur de pointeur nulle ou une valeur de pointeur de membre nulle est convertie en false ; toute autre valeur est convertie en true.
Cela signifie que operator int ()
est également applicable.
2.3 Quelle fonction de conversion est sélectionnée ?
La sélection de la fonction de conversion appropriée est effectuée via la résolution de surcharge ( C++11, 13.3.1.5/1 [over.match.conv] (en anglais) ):
La résolution de surcharge est utilisée pour sélectionner la fonction de conversion à invoquer.
Il existe une "bizarrerie" particulière en ce qui concerne la résolution des surcharges pour les fonctions membres d'une classe : le paramètre objet implicite".
Por C++11, 13.3.1 [over.match.funcs] (en anglais) ,
[...] les fonctions membres statiques et non statiques ont toutes un paramètre objet implicite [...].
où le type de ce paramètre pour les fonctions membres non statiques - conformément à la clause 4- est :
-
"référence lvalue à cv X" pour les fonctions déclarées sans qualificatif de ref ou avec le qualificatif de ref &.
-
"rvalue reference to cv X" pour les fonctions déclarées avec le qualificatif && ref-qualifier
où X est la classe dont la fonction est membre et cv est la qualification cv de la déclaration de la fonction membre.
Cela signifie que (par C++11, 13.3.1.5/2 [over.match.conv] ), dans une fonction d'initialisation par conversion,
[La liste d'arguments a un seul argument, qui est l'expression de l'initialisateur. [Note : Cet argument sera comparé au paramètre objet implicite des fonctions de conversion. -fin de la note ]
Les fonctions candidates à la résolution des surcharges sont les suivantes :
operator bool (B2 const &);
operator int (B2 &);
Évidemment, operator int ()
est une meilleure correspondance si une conversion est demandée en utilisant un objet non constant de type B2
depuis operator bool ()
a nécessité une conversion de qualification.
Si les deux fonctions de conversion partagent la même qualification const, la résolution de surcharge de ces fonctions ne fera plus l'affaire. Dans ce cas, le classement de la conversion (séquence) entre en jeu.
3. Pourquoi le operator bool ()
sélectionné lorsque les deux fonctions de conversion partagent la même qualification const ?
La conversion de B2
à bool
est une séquence de conversion définie par l'utilisateur ( C++11, 13.3.3.1.2/1 [over.ics.user] )
Une séquence de conversion définie par l'utilisateur se compose d'une première séquence de conversion standard, suivie d'une conversion définie par l'utilisateur, puis d'une deuxième séquence de conversion standard.
[...] Si la conversion définie par l'utilisateur est spécifiée par une fonction de conversion, la séquence de conversion standard initiale convertit le type source en paramètre objet implicite de la fonction de conversion.
C++11, 13.3.3.2/3 [over.ics.rank]
[...] définit un ordre partiel de séquences de conversion implicites basé sur les relations meilleure séquence de conversion et meilleure conversion.
[...] 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 elles contiennent la même fonction de conversion définie par l'utilisateur ou le même constructeur ou la même initialisation d'agrégat 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.
La deuxième conversion standard est le cas de operator bool()
es bool
à bool
(conversion d'identité) alors que la seconde conversion standard en cas de operator int ()
es int
à bool
qui est une conversion booléenne.
Par conséquent, la séquence de conversion, utilisant operator bool ()
Il est préférable que les deux fonctions de conversion partagent la même qualification const.