web-dev-qa-db-de.com

Ternärer Operator, der implizit in die Basisklasse umgewandelt wird

Betrachten Sie diesen 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;
}

Dies funktioniert nicht unter Clang oder GCC.

fehler: Bedingter Ausdruck zwischen den verschiedenen Zeigertypen 'Foo *' und 'Bar *' hat keinen Cast Base * obj =! bar? foo: bar;

Was bedeutet, dass ich zum Kompilieren den Code ändern muss in:

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

Da eine implizite Besetzung eines Base* existiert, was hindert den Compiler daran?

Mit anderen Worten, warum macht Base* obj = foo; Arbeit ohne Besetzung, aber mit dem ?: Operator nicht? Liegt es daran, dass nicht klar ist, ob ich den Teil Base verwenden möchte?

48

Zitat aus dem C++ - Standardentwurf N4296, Abschnitt 5.16 Bedingter Operator , Absatz 6.3:

  • Der zweite und/oder der dritte Operand haben einen Zeigertyp. Zeigerumwandlungen (4.10) und Qualifikationsumwandlungen (4.4) werden durchgeführt, um sie zu ihrem zusammengesetzten Zeigertyp zu bringen (Abschnitt 5). Das Ergebnis ist vom zusammengesetzten Zeigertyp.

Abschnitt 5 Ausdrücke , Absätze 13.8 und 13.9:

Der zusammengesetzte Zeigertyp von zwei Operanden p1 und p2 mit den Typen T1 bzw. T2, wobei mindestens einer ein Zeiger oder ein Zeiger auf den Elementtyp oder std :: nullptr_t ist, ist:

  • wenn T1 und T2 ähnliche Typen sind (4.4), der cv-kombinierte Typ von T1 und T2;
  • andernfalls ist ein Programm, das die Bestimmung eines zusammengesetzten Zeigertyps erfordert, fehlerhaft.

Hinweis: Ich habe 5/13.8 hierher kopiert, um Ihnen zu zeigen, dass es nicht trifft. Was tatsächlich in Kraft ist, ist 5/13.9, "das Programm ist schlecht geformt".

Und Abschnitt 4.10 Zeigerumwandlungen , Absatz 3:

Ein Wert vom Typ "Zeiger auf cv D", wobei D ein Klassentyp ist, kann in einen Wert vom Typ "Zeiger auf cv B" konvertiert werden, wobei B eine Basisklasse (Abschnitt 10) von D ist Unzugängliche (Klausel 11) oder nicht eindeutige (10.2) Basisklasse von D, ein Programm, das diese Konvertierung erfordert, ist nicht korrekt. Das Ergebnis der Konvertierung ist ein Zeiger auf das Basisklassen-Unterobjekt des abgeleiteten Klassenobjekts. Der Nullzeigerwert wird in den Nullzeigerwert des Zieltyps konvertiert.

Es spielt also (überhaupt) keine Rolle, dass sowohl Foo als auch Bar von derselben Basisklasse abgeleitet sind. Es ist nur wichtig, dass ein Zeiger auf Foo und ein Zeiger auf Bar nicht ineinander konvertierbar sind (keine Vererbungsbeziehung).

21
iBug

Das Zulassen einer Konvertierung in den Basiszeigertyp für den bedingten Operator klingt gut, wäre aber in der Praxis problematisch.

In deinem Beispiel

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

Es mag naheliegend erscheinen, als Typ für cond ? foo : barBase* Zu wählen.

Aber diese Logik hält einem allgemeinen Fall nicht stand

Z.B.:

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

Sollte cond ? foo : bar Vom Typ Base* Oder vom Typ TheRealBase* Sein?

Wie wäre es mit:

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

Welcher Typ sollte cond ? foo : bar Jetzt sein?

Oder wie wäre es jetzt:

struct Base {};

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

struct X {};

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


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

Autsch!! Viel Glück für eine Art cond ? foo : bar. Ich weiß, hässlich hässlich, unpraktisch und würdig gejagt zu werden, aber der Standard müsste noch Regeln dafür haben.

Du verstehst den Punkt.

Denken Sie auch daran, dass std::common_type In den Regeln für bedingte Operatoren definiert ist.

19
bolov

Mit anderen Worten, warum funktioniert Base* obj = foo; Ohne Cast, die Verwendung des Operators ?: Jedoch nicht?

Der Typ des bedingten Ausdrucks hängt nicht davon ab, wem er zugewiesen ist. In Ihrem Fall muss der Compiler in der Lage sein, !bar ? foo : bar; Unabhängig von der Zuordnung auszuwerten.

In Ihrem Fall ist dies ein Problem, da weder foo in den Typ bar noch bar in den Typ foo konvertiert werden kann.

Liegt es daran, dass nicht klar ist, ob ich das Basisteil verwenden möchte?

Genau.

5
R Sahu

Da eine implizite Besetzung eines Base* existiert, was hindert den Compiler daran?

Nach [expr.cond]/7

LWert-zu-rWert-, Array-zu-Zeiger- und Funktions-zu-Zeiger-Standardkonvertierungen werden für den zweiten und dritten Operanden ausgeführt. Nach diesen Umwandlungen gilt Folgendes:

  • ...
  • Der zweite und/oder der dritte Operand haben einen Zeigertyp. Zeigerkonvertierungen, Funktionszeigerkonvertierungen und Qualifizierungskonvertierungen werden durchgeführt, um sie auf ihren zusammengesetzten Zeigertyp zu bringen. Das Ergebnis ist vom zusammengesetzten Zeigertyp.

wobei zusammengesetzter Zeigertyp in [Ausdruckstyp]/4 definiert ist:

Der zusammengesetzte Zeigertyp von zwei Operanden p1 und p2 mit den Typen T1 bzw. T2, wobei mindestens einer ein Zeiger oder ein Zeiger-auf-Element-Typ oder std :: nullptr_t ist, ist:

  • wenn sowohl p1 als auch p2 Nullzeiger-Konstanten sind, std :: nullptr_t;

  • wenn entweder p1 oder p2 eine Nullzeiger-Konstante ist, T2 bzw. T1;

  • wenn T1 oder T2 "Zeiger auf cv1 void" ist und der andere Typ "Zeiger auf cv2 T" ist, wobei T ein Objekttyp oder void ist, "Zeiger auf cv12 void", wobei cv12 die Vereinigung von cv1 und cv2 ist;

  • wenn T1 oder T2 "Zeiger auf keine Ausnahmefunktion" ist und der andere Typ "Zeiger auf Funktion" ist, wobei die Funktionstypen ansonsten gleich sind, "Zeiger auf Funktion";

  • wenn T1 "Zeiger auf cv1 C1" und T2 "Zeiger auf cv2 C2" ist, wobei C1 in Bezug auf C2 steht oder C2 in Bezug auf C1 steht, ist der cv-kombinierte Typ von T1 und T2 oder der cv-kombinierte Art von T2 bzw. T1;

  • wenn T1 "Zeiger auf Mitglied von C1 vom Typ cv1 U1" ist und T2 "Zeiger auf Mitglied von C2 vom Typ cv2 U2" ist, wobei C1 in Bezug auf C2 steht oder C2 in Bezug auf C1 steht, ist der cv-kombinierte Typ von T2 und T1 oder dem cv-kombinierten Typ von T1 bzw. T2;

  • wenn T1 und T2 ähnliche Typen sind, der cv-kombinierte Typ von T1 und T2;

  • andernfalls ist ein Programm, das die Bestimmung eines zusammengesetzten Zeigertyps erfordert, fehlerhaft.

Jetzt können Sie sehen, dass ein Zeiger auf "gemeinsame Basis" nicht der zusammengesetzte Zeigertyp ist.


Mit anderen Worten, warum macht Base* obj = foo; Arbeit ohne Besetzung, aber mit dem ?: Operator nicht? Liegt es daran, dass nicht klar ist, ob ich den Teil Base verwenden möchte?

Das Problem ist, dass die Regel den Typ des bedingten Ausdrucks unabhängig erkennen sollte, ohne die Initialisierung zu beachten.

Um genau zu sein, sollte eine Regel erkennen, dass die Bedingungsausdrücke in den folgenden beiden Anweisungen vom selben Typ sind.

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

Nun, wenn Sie keinen Zweifel haben, dass der bedingte Ausdruck in der zweiten Aussage falsch ist1Was ist die Begründung, um es in der ersten Aussage wohlgeformt zu machen?


1 Natürlich kann man eine Regel aufstellen, um einen solchen Ausdruck gut zu formen. Lassen Sie beispielsweise zusammengesetzte Zeigertypen den Zeiger auf einen eindeutigen Basistyp einschließen. Dies steht jedoch außer Frage und sollte vom ISO-C++ - Ausschuss erörtert werden.

3
xskxzr