57 votes

Opération implicite ternaire sur la classe de base

Considérer ce morceau de code:

struct Base
{
    int x;
};

struct Bar : Base
{
    int y;
};

struct Foo : Base
{
    int z;
};

Bar* bar = new Bar;
Foo* foo = new Foo;

Base* returnBase()
{
    Base* obj = !bar ? foo : bar;
    return obj;
}

int main() {
    returnBase();
    return 0;
}

Cela ne fonctionne pas sous Clang ou GCC, ce qui me donne :

erreur: expression conditionnelle entre les différents types de pointeur ‘Foo*' et ‘*' manque un cast de Base* obj = !bar ? foo : bar;

Ce qui signifie pour compiler, j'ai modifier le code pour :

Base* obj = !bar ? static_cast<Base*>(foo) : bar;

Depuis un cast implicite à un Base* existe, ce qui est d'empêcher le compilateur de le faire?

En d'autres termes, pourquoi est - Base* obj = foo; travail sans un plâtre, mais à l'aide de l' ?: opérateur n'en a pas? Est-ce parce qu'il n'est pas clair que je veux utiliser l' Base de la partie?

28voto

bolov Points 4005

Permettant une conversion à la base type de pointeur pour l'opérateur conditionnel sonne bien, mais serait problématique dans la pratique.

Dans votre exemple

struct Base {};
struct Foo : Base {};
struct Bar : Base {};

Il peut sembler comme le choix évident pour le type d' cond ? foo : bar être Base*.

Mais cette logique ne tient pas pour un cas général

E. g.:

struct TheRealBase {};
struct Base : TheRealBase {};
struct Foo : Base {};
struct Bar : Base {};

Devrait - cond ? foo : bar être de type Base* ou de type TheRealBase*?

Comment à ce sujet:

struct Base1 {};
struct Base2 {};
struct Foo : Base1, Base2 {};
struct Bar : Base1, Base2 {};

Ce type devrait cond ? foo : bar maintenant?

Ou que diriez-vous maintenant:

struct Base {};

struct B1 : Base {};
struct B2 : Base {};

struct X {};

struct Foo : B1, X {};
struct Bar : B2, X {};


      Base
      /  \
     /    \   
    /      \
  B1        B2 
   |   X    |
   | /   \  |
   |/     \ |
  Foo      Bar

Ouch!! Bonne chance raisonnement pour un type d' cond ? foo : bar. Je sais, laid laid, non-pratique et la chasse digne, mais la norme serait de toujours avoir des règles pour cela.

Vous obtenez le point.

Et aussi garder à l'esprit qu' std::common_type est définie en fonction de l'opérateur conditionnel règles.

27voto

iBug Points 20818

Citant le standard C++ projet de N4296, Section 5.16 opérateur Conditionnel, Paragraphe 6.3:

  • L'un ou les deux de la deuxième et de la troisième opérandes ont type pointeur; pointeur de conversions (4.10) et qualification des conversions (4.4) sont effectuées pour les amener à leur composite de type pointeur (Alinéa 5). Le résultat est du composite de type pointeur.

Section 5 Expressions, Paragraphe 13.8 et 13.9:

Le composite de type pointeur de deux opérandes p1 et p2 ayant des types T1 et T2, respectivement, où, à l' moins un est un pointeur de pointeur ou de type de membre ou std::nullptr_t, est:

  • si T1 et T2 sont des types similaires (4.4), le cv-type de T1 et T2;
  • sinon, un programme qui nécessite la détermination d'un composé de type pointeur est mal formé.

Note: j'ai copié 5/13.8 ici, juste pour vous montrer qu'il ne frappe pas. Ce qui est fait, en effet, est 5/13.9, "le programme est mal formé".

Et à l'Article 4.10 Pointeur de conversions, Paragraphe 3:

Un prvalue de type "pointeur de cv D", où D est un type de classe, peuvent être convertis en un prvalue de type "pointeur cv B", où B est une classe de base (Clause 10) de D. Si B est un inaccessible (Clause 11) ou ambigu (10.2) de la classe de base de D, un programme qui nécessite cette conversion est mal formé. Le résultat de la conversion est un pointeur vers la classe de base sous-objet de l'objet de classe dérivée. La valeur de pointeur null est converti à l' null la valeur du pointeur du type de destination.

Donc, il n'importe pas (du tout) que les deux Foo et Bar sont dérivées d'une même classe de base. Il importe seulement qu'un pointeur vers Toto et un pointeur vers le Bar ne sont pas convertibles les unes aux autres (pas de relation d'héritage).

6voto

R Sahu Points 24027

En d'autres termes, pourquoi est - Base* obj = foo; travail sans un plâtre, mais à l'aide de l' ?: opérateur n'en a pas?

Le type de l'expression conditionnelle ne dépend pas de la ce qui elle est attribuée. Dans votre cas, le compilateur a besoin pour être en mesure d'évaluer !bar ? foo : bar; indépendamment de ce qu'il est attribué.

Dans votre cas, c'est un problème puisque ni l' foo convertis en type d' bar ni bar peut être converti type d' foo.

Est-ce parce qu'il n'est pas clair que je veux utiliser la pièce de Base?

Précisément.

4voto

xskxzr Points 5160

Depuis un cast implicite à un Base* existe, ce qui est d'empêcher le compilateur de le faire?

Selon [expr.cond]/7,

Lvalue-à-rvalue, tableau de pointeur, et la fonction de pointeur standard conversions sont effectuées sur le deuxième et le troisième opérande. Après ces transformations, l'une des opérations suivantes doivent contenir:

  • ...
  • L'un ou les deux de la deuxième et de la troisième opérandes ont type pointeur; pointeur de conversions, le pointeur de la fonction de conversions, de qualification et de conversions sont effectuées pour les amener à leur composite de type pointeur. Le résultat est du composite de type pointeur.

composite de type pointeur est défini dans [expr.type]/4:

Le composite de type pointeur de deux opérandes p1 et p2 ayant des types T1 et T2, respectivement, où au moins l'un est un pointeur de pointeur ou de type de membre ou std::nullptr_t, est:

  • si les deux p1 et p2 sont de pointeur null constantes, std::nullptr_t;

  • si p1 ou p2 est un pointeur null constante, T2 ou T1, respectivement;

  • si T1 ou T2 est "pointeur vers cv1 vide" et l'autre de type "pointeur vers cv2 T, où T est un type d'objet ou nulle, de "pointeur de cv12 vide", où cv12 est l'union de cv1 et cv2;

  • si T1 ou T2 est "pointeur vers noexcept fonction" et l'autre de type "pointeur de fonction", où la fonction des types sont par ailleurs le même, "le pointeur de fonction";

  • si T1 est "pointeur vers cv1 C1" et T2 est "pointeur à cv2 C2", où C1 est la référence liés à C2 ou C2 est une référence liée à la C1, le cv-type de T1 et de T2 ou la cv-type de T2 et T1, respectivement;

  • si T1 est "pointeur de membre de la C1 de type cv1 U1" et T2 est "pointeur de membre de la C2 de type cv2 U2" où C1 est la référence liés à C2 ou C2 est une référence liée à la C1, le cv-type de T2 et T1 ou le cv-type de T1 et T2, respectivement;

  • si T1 et T2 sont des types similaires, le cv-type de T1 et T2;

  • sinon, un programme qui nécessite la détermination d'un composé de type pointeur est mal formé.

Maintenant, vous pouvez voir un pointeur à la "base commune" n'est pas le composite de type pointeur.


En d'autres termes, pourquoi est - Base* obj = foo; travail sans un plâtre, mais à l'aide de l' ?: opérateur n'en a pas? Est-ce parce qu'il n'est pas clair que je veux utiliser l' Base de la partie?

Le problème est que la règle devrait détecter le type de l'expression conditionnelle de façon indépendante, sans l'observation de l'initialisation.

Pour être plus précis, une règle doit détecter les expressions conditionnelles dans les deux instructions suivantes pour être du même type.

Base* obj = !bar ? foo : bar;
bar ? foo : bar;

Maintenant, si vous n'avez pas de doute que l'expression conditionnelle dans la deuxième instruction est mal formé1, quel est le raisonnement pour le rendre bien formé dans la première déclaration?


1 bien sûr, on peut faire une règle pour créer une telle expression bien formée. Par exemple, supposons composite de type pointeur inclure le pointeur à un sans ambiguïté le type de base. Cependant, ce est quelque chose au-delà de cette question, et devrait être discuté par l'ISO C++ comité.

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