79 votes

Différents opérateurs de cast appelés par différents compilateurs

Considérons le court programme C++ suivant :

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Si je le compile sur différents compilateurs, j'obtiens des résultats différents. Avec Clang 3.4 et GCC 4.4.7 il imprime true alors que Visual Studio 2013 imprime false ce qui signifie qu'ils font appel à des opérateurs de cast différents au niveau de (bool)b . Quel est le comportement correct selon la norme ?

Dans ma compréhension operator bool() ne nécessite aucune conversion, tandis que operator int() nécessiterait un int à bool le compilateur doit donc choisir la première. Est-ce que const faire quelque chose avec ça, est-ce que la conversion en const est considérée comme plus "coûteuse" par le compilateur ?

Si je retire le const tous les compilateurs produisent également false en sortie. Par contre, si je combine les deux classes ensemble (les deux opérateurs seront dans la même classe) les trois compilateurs produiront true sortie.

51voto

La norme stipule :

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.

§12.3 [class.conv]

Ce qui signifie que operator bool n'est pas caché par operator int .

La norme stipule :

Pendant la résolution de la surcharge, l'argument objet implicite est indiscernable des autres arguments.

§13.3.3.1 [over.match.funcs]

L'"argument de l'objet implicite" dans ce cas est b qui est de type B2 & . operator bool nécessite const B2 & donc le compilateur devra ajouter const à b d'appeler operator bool . Ceci, toutes choses égales par ailleurs, rend operator int une meilleure correspondance.

La norme stipule qu'un static_cast (que le cast de style C effectue dans ce cas) peut être converti en un type T (dans ce cas int ) si :

la déclaration T t(e); est bien formé, pour une certaine variable temporaire inventée t .

§5.2.9 [expr.static.cast]

Par conséquent, le int peut être converti en un bool et un bool peut également être converti en un bool .

La norme stipule :

Les fonctions de conversion de S et ses classes de base sont considérées. Les fonctions de conversion non explicites qui ne sont pas cachées dans les classes de base sont considérées. S et le type de rendement T ou un type qui peut être converti en type T via une séquence de conversion standard sont des fonctions candidates.

§13.3.1.5 [over.match.conv] (en anglais)

L'ensemble des surcharges est donc composé de operator int y operator bool . Toutes choses étant égales par ailleurs, operator int est une meilleure solution (puisque vous n'avez pas à ajouter de constance). Par conséquent, operator int doit être sélectionné.

Notez que (peut-être contre l'intuition) la norme ne considère pas le type de retour (c'est-à-dire le type vers lequel ces opérateurs se convertissent) une fois qu'ils ont été ajoutés à l'ensemble des surcharges (comme établi ci-dessus), à condition que la séquence de conversion pour les arguments de l'un d'entre eux soit supérieure à la séquence de conversion pour les arguments de l'autre (ce qui, en raison de la constance, est le cas dans ce cas).

La norme stipule :

Compte tenu de ces définitions, 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), et alors

  • pour un certain argument j, ICSj(F1) est une meilleure séquence de conversion que ICSj(F2), ou, si ce n'est pas le cas,
  • le contexte est une initialisation par conversion définie par l'utilisateur et la séquence de conversion standard du type de retour de F1 au type de destination (c'est-à-dire le type de l'entité à initialiser) est une meilleure séquence de conversion que la séquence de conversion standard du type de retour de F2 au type de destination.

§13.3.3 [sur.match.meilleur]

Dans ce cas, il n'y a qu'un seul argument (l'implicite this paramètre). La séquence de conversion pour B2 & => B2 & (pour appeler operator int ) est supérieur à B2 & => const B2 & (pour appeler operator bool ), et donc operator int est sélectionné dans l'ensemble des surcharges sans tenir compte du fait qu'il ne se convertit pas directement en bool .

9voto

Pixelchemist Points 3636

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.

1voto

Debasish Jana Points 1051

Le type bool du C++ a deux valeurs - true et false avec les valeurs correspondantes 1 et 0. La confusion inhérente peut être évitée si vous ajoutez un opérateur bool dans la classe B2 qui appelle explicitement l'opérateur bool de la classe de base(B), alors la sortie est fausse. Voici mon programme modifié. Alors l'opérateur bool signifie l'opérateur bool et en aucun cas l'opérateur int.

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
    operator bool() {
        return B::operator bool();
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Dans votre exemple, (bool) b essayait d'appeler l'opérateur bool pour B2, B2 a hérité de l'opérateur bool, et de l'opérateur int, par la règle de dominance, l'opérateur int est appelé et l'opérateur bool hérité dans B2. Cependant, en ayant explicitement un opérateur bool dans la classe B2 elle-même, le problème est résolu.

0voto

umlcat Points 2025

Certaines des réponses précédentes fournissent déjà beaucoup d'informations.

Ma contribution est que les "opérations coulées" sont compilées de manière similaire aux "opérations surchargées". Je suggère de créer une fonction avec un identifiant unique pour chaque opération, et plus tard, de la remplacer par l'opérateur ou le coulage requis.

#include <iostream>

class B {
public:
    bool ToBool() const {
        return false;
    }
};

class B2 : public B {
public:
    int ToInt() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << b.ToBool() << std::endl;
}

Et, plus tard, appliquer l'opérateur ou la fonte.

#include <iostream>

class B {
public:
    operator bool() {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Juste mes deux centimes.

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