Programm wird in 3 großen C ++ - Compilern unterschiedlich kompiliert. Welcher ist richtig?


116

Als interessante Folge (allerdings nicht von großer praktischer Bedeutung) zu meiner vorherigen Frage: Warum erlaubt C ++ uns, den Variablennamen beim Deklarieren einer Variablen in Klammern zu setzen?

Ich fand heraus, dass das Kombinieren der Deklaration in Klammern mit der Funktion für injizierte Klassennamen zu überraschenden Ergebnissen hinsichtlich des Compilerverhaltens führen kann.

Schauen Sie sich folgendes Programm an:

#include <iostream>
struct B
{
};

struct C
{
  C (){ std::cout << "C" << '\n'; }
  C (B *) { std::cout << "C (B *)" << '\n';}
};

B *y = nullptr;
int main()
{
  C::C (y);
}
  1. Das Kompilieren mit g ++ 4.9.2 führt zu folgendem Kompilierungsfehler:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Es wird erfolgreich mit MSVC2013 / 2015 kompiliert und gedruckt C (B *)

  3. Es wird erfolgreich mit clang 3.5 kompiliert und gedruckt C

Die obligatorische Frage ist also, welche richtig ist. :) :)

(Ich schwankte jedoch stark in Richtung Clang-Version und msvc Weg, um die Deklaration von Variablen zu stoppen, nachdem ich nur den Typ mit technisch geändert habe, scheint sein Typedef irgendwie seltsam)


3
C::C y;macht keinen Sinn, oder? Auch nicht C::C (y); Zuerst dachte ich , das war eine Instanz von Most-Vexing-Parse stackoverflow.com/questions/tagged/most-vexing-parse , aber jetzt denke ich , es ist nur nicht definiertes Verhalten bedeutet , alle drei Compiler sind „richtig.“
Dale Wilson

4
# 3 clang ist definitiv falsch, # 2 msvc ist zu freizügig und # 1 g ++ ist richtig ((ich denke)

8
C::Cbenennt keinen Typ, sondern eine Funktion, daher ist GCC imo richtig.
Galik


Antworten:


91

GCC ist korrekt, zumindest gemäß den C ++ 11-Suchregeln. 3.4.3.1 [class.qual] / 2 gibt an, dass der verschachtelte Namensbezeichner, wenn er mit dem Klassennamen identisch ist, auf den Konstruktor und nicht auf den injizierten Klassennamen verweist. Es gibt Beispiele:

B::A ba;           // object of type A
A::A a;            // error, A::A is not a type name
struct A::A a2;    // object of type A

Es sieht aus wie MSVC misinterprets es als funktions Casts Ausdruck eine temporäre Erstellung Cmit yals Konstruktor Parameter; und Clang interpretiert es falsch als Deklaration einer Variablen yvom Typ C.


2
Ja, 3.4.3.1/2 ist der Schlüssel. Gut gemacht!
Leichtigkeitsrennen im Orbit

Es heißt "In einer Suche, in der Funktionsnamen nicht ignoriert werden". Es scheint mir, dass in den angegebenen Beispielen insbesondere A::A a;Funktionsnamen ignoriert werden sollten - oder nicht?
Columbo

1
Nach der Nummerierung in N4296 lautet der Schlüssel tatsächlich 3.4.3.1/2.1: "Wenn der nach dem verschachtelten Namensbezeichner angegebene Name beim Nachschlagen in C der Name der injizierten Klasse von C ist [...] Der Name wird stattdessen als Name des Konstruktors der Klasse C angesehen. " Mikes Zusammenfassung ist jedoch etwas zu stark vereinfacht. Beispielsweise würde ein typedef des Klassennamens innerhalb der Klasse einem verschachtelten Namensbezeichner, der sich vom Klassennamen unterscheidet, erlauben, weiterhin auf den Klassennamen zu verweisen, sodass er sich weiterhin auf den bezieht ctor.
Jerry Coffin

2
@Mgetz: Aus der Frage: "Es wird erfolgreich mit MSVC2013 / 2015 kompiliert und gedruckt C (B *)" .
Leichtigkeitsrennen im Orbit

2
Der Vollständigkeit halber sollte hiermit klargestellt werden, ob es mit der erforderlichen Diagnose schlecht oder ohne erforderliche Diagnose schlecht ausgebildet ist. Wenn letzteres der Fall ist, sind alle Compiler "richtig".
MM

16

G ++ ist korrekt, da es einen Fehler gibt. Weil der Konstruktor ohne newOperator nicht direkt in einem solchen Format aufgerufen werden konnte . Und obwohl Ihr Code aufruft C::C, sieht es aus wie ein Konstruktoraufruf. Gemäß dem C ++ 11-Standard 3.4.3.1 ist dies jedoch kein zulässiger Funktionsaufruf oder Typname ( siehe Antwort von Mike Seymour ).

Clang ist falsch, da es nicht einmal die richtige Funktion aufruft.

MSVC ist etwas Vernünftiges, aber es folgt immer noch nicht dem Standard.


2
Was ändert der newBediener?
Neil Kirk

1
@NeilKirk: Sehr viel für Leute, die denken, dass dies new B(1,2,3)eine Art "direkter Konstruktoraufruf" ist (was natürlich nicht der Fall ist), der sich von der temporären Instanziierung B(1,2,3)oder der Deklaration unterscheidet B b(1,2,3).
Leichtigkeitsrennen im Orbit

@LightningRacisinObrit Wie würden Sie beschreiben, was new B(1,2,3)ist?
user2030677

1
@ user2030677: Ein neuer Ausdruck, der das Schlüsselwort new, einen Typnamen und eine Konstruktorargumentliste verwendet. Es ist immer noch kein "direkter Konstruktoraufruf".
Leichtigkeitsrennen im Orbit

„Clang ist falsch , da es sich auch nicht die richtige Funktion aufrufen.“: Ich denke , (weil die Bemerkung des OP über Klammern in Erklärungen) , dass Clang Interpretes C::C (y); wie C::C y;, dh eine Defintion einer Variablen y vom Typ C (die injizierte Typ C unter Verwendung von : : C, während fälschlicherweise die zunehmend verrückte Sprachspezifikation 3.4.1.2 ignoriert wird, die C :: C zum Konstruktor macht). Das ist kein eklatanter Fehler, wie Sie zu denken scheinen, imo.
Peter - Monica am
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.