web-dev-qa-db-fra.com

Opérateur ternaire transtypage implicite en classe de base

Considérez 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: il n’existe pas d’expression conditionnelle entre les types de pointeurs distincts "Foo *" et "Bar *". Base * obj =! bar? foo: bar;

Ce qui signifie que pour compiler, je dois changer le code en:

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

Depuis une conversion implicite en Base* existe, qu'est-ce qui empêche le compilateur de le faire?

En d'autres termes, pourquoi Base* obj = foo; travailler sans plâtre mais en utilisant le ?: l'opérateur ne fonctionne pas? Est-ce parce qu'il n'est pas clair que je souhaite utiliser la partie Base?

48
Sombrero Chicken

Citant le projet de norme C++ N4296, section 5.16 Opérateur conditionnel , paragraphe 6.3:

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

Section 5 Expressions , paragraphes 13.8 et 13.9:

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

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

Remarque: J'ai copié 5/13.8 ici juste pour vous montrer qu'il ne frappe pas. Ce qui est effectivement en vigueur est 5/13,9, "le programme est mal formé".

Et Section 4.10 Conversions de pointeur , paragraphe 3:

Une valeur de type "pointeur vers cv D", où D est un type de classe, peut être convertie en une valeur de type "pointeur vers cv B", où B est une classe de base (article 10) de D. Si B est un inaccessible (article 11) ou ambiguë (10.2) classe de base de D, un programme qui nécessite cette conversion est mal formé. Le résultat de la conversion est un pointeur sur le sous-objet de classe de base de l'objet de classe dérivé. La valeur du pointeur nul est convertie en la valeur du pointeur nul du type de destination.

Donc, peu importe (du tout) que Foo et Bar dérivent d'une même classe de base. Il importe seulement qu'un pointeur vers Foo et un pointeur vers Bar ne soient pas convertibles l'un à l'autre (pas de relation d'héritage).

21
iBug

Autoriser une conversion au type de pointeur de base pour l'opérateur conditionnel sonne bien mais serait problématique en pratique.

Dans votre exemple

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

Cela peut sembler le choix évident pour le type de cond ? foo : bar être Base*.

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

Par exemple.:

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

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

Que diriez-vous:

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

Quel type doit cond ? foo : bar être 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

Aie!! Bonne chance pour un type de cond ? foo : bar. Je sais, moche moche, non pratique et chassé digne, mais la norme devrait encore avoir des règles pour cela.

Tu obtiens le point.

Et gardez également à l'esprit que std::common_type est défini en termes de règles d'opérateur conditionnel.

19
bolov

En d'autres termes, pourquoi Base* obj = foo; travailler sans plâtre mais en utilisant le ?: l'opérateur ne fonctionne pas?

Le type de l'expression conditionnelle ne dépend pas de ce à quoi elle est affectée. Dans votre cas, le compilateur doit pouvoir évaluer !bar ? foo : bar; indépendamment de ce à quoi il est affecté.

Dans votre cas, c'est un problème car ni foo converti en type de bar ni bar ne peut être converti en type de foo.

Est-ce parce qu'il n'est pas clair que je souhaite utiliser la partie Base?

Précisément.

5
R Sahu

Depuis une conversion implicite en Base* existe, qu'est-ce qui empêche le compilateur de le faire?

Selon [expr.cond]/7,

Les conversions standard Lvalue en rvalue, tableau en pointeur et fonction en pointeur sont effectuées sur les deuxième et troisième opérandes. Après ces conversions, l'une des actions suivantes doit être respectée:

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

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

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

  • si p1 et p2 sont des constantes de pointeur nul, std :: nullptr_t;

  • si p1 ou p2 est une constante de pointeur nulle, T2 ou T1, respectivement;

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

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

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

  • si T1 est "pointeur vers un membre de C1 de type cv1 U1" et T2 est "pointeur vers un membre de C2 de type cv2 U2" où C1 est lié à C2 ou C2 est lié à C1, le type combiné cv de T2 et T1 ou du type combiné cv de T1 et T2, respectivement;

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

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

Vous pouvez maintenant voir qu'un pointeur sur "base commune" n'est pas le type de pointeur composite.


En d'autres termes, pourquoi Base* obj = foo; travailler sans plâtre mais en utilisant le ?: l'opérateur ne fonctionne pas? Est-ce parce qu'il n'est pas clair que je souhaite utiliser la partie Base?

Le problème est que la règle doit détecter le type de l'expression conditionnelle indépendamment, sans observer l'initialisation.

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

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

Maintenant, si vous ne doutez pas que l'expression conditionnelle dans la deuxième déclaration est mal formée1, quel est le raisonnement pour le rendre bien formé dans la première déclaration?


1 Bien sûr, on peut établir une règle pour que cette expression soit bien formée. Par exemple, laissez les types de pointeurs composites inclure le pointeur vers un type de base non ambigu. Cependant, c'est quelque chose au-delà de cette question, et devrait être discuté par le comité ISO C++.

3
xskxzr