Wir haben eine viel größere Anwendung, die auf der Vorlagenüberladung von char- und const char-Arrays beruht. In gcc 7.5, clang und visual studio gibt der folgende Code für alle Fälle "NON-CONST" aus. Für gcc 8.1 und höher wird die Ausgabe jedoch unten angezeigt:
#include <iostream>
class MyClass
{
public:
template <size_t N>
MyClass(const char (&value)[N])
{
std::cout << "CONST " << value << '\n';
}
template <size_t N>
MyClass(char (&value)[N])
{
std::cout << "NON-CONST " << value << '\n';
}
};
MyClass test_1()
{
char buf[30] = "test_1";
return buf;
}
MyClass test_2()
{
char buf[30] = "test_2";
return {buf};
}
void test_3()
{
char buf[30] = "test_3";
MyClass x{buf};
}
void test_4()
{
char buf[30] = "test_4";
MyClass x(buf);
}
void test_5()
{
char buf[30] = "test_5";
MyClass x = buf;
}
int main()
{
test_1();
test_2();
test_3();
test_4();
test_5();
}
Die Ausgabe von gcc 8 und 9 (von godbolt) lautet:
CONST test_1
NON-CONST test_2
NON-CONST test_3
NON-CONST test_4
NON-CONST test_5
Dies scheint mir ein Compiler-Fehler zu sein, aber ich denke, es könnte sich um ein anderes Problem im Zusammenhang mit einem Sprachwechsel handeln. Weiß jemand definitiv?
Antworten:
Wenn Sie einen einfachen ID-Ausdruck von einer Funktion zurückgeben (die ein lokales Funktionsobjekt festgelegt hat), muss der Compiler die Überladungsauflösung zweimal durchführen. Zuerst behandelt es es so, als wäre es ein Wert und kein Wert. Nur wenn die erste Überlastungsauflösung fehlschlägt, wird sie erneut mit dem Objekt als Wert ausgeführt.
Wenn wir eine rvalue-Überladung hinzufügen würden,
Die Ausgabe wird
und das wäre richtig. Was nicht korrekt ist, ist das Verhalten von GCC, wie Sie es sehen. Die erste Überlastungslösung wird als Erfolg gewertet. Dies liegt daran, dass eine Konstantenwertreferenz möglicherweise an einen Wert gebunden ist. Der Text wird jedoch ignoriert, "oder wenn der Typ des ersten Parameters des ausgewählten Konstruktors kein r-Wert-Verweis auf den Objekttyp ist" . Demnach muss das Ergebnis der ersten Überlastungsauflösung verworfen und erneut ausgeführt werden.
Nun, das ist sowieso die Situation bis C ++ 17. Der aktuelle Standardentwurf sagt etwas anderes.
Der Text von bis zu C ++ 17 wurde entfernt. Es ist also ein Zeitreisefehler. GCC implementiert das C ++ 20-Verhalten, dies jedoch auch dann, wenn der Standard C ++ 17 ist.
quelle
clang++
C ++ 20-Implementierung auch nicht korrekt ist, da sie in allen Fällen die NON-CONST-Version im Originalcode verwendet.Es gibt eine Debatte darüber, ob dies "intuitives Verhalten" in den Kommentaren ist oder nicht, daher dachte ich, ich würde die Gründe für dieses Verhalten auf den Prüfstand stellen.
Bei CPPCON wurde ein ziemlich netter Vortrag gehalten, der mir dies etwas klarer macht { talk , slide ]. Was bedeutet im Grunde eine Funktion, die eine nicht konstante Referenz verwendet? Dass das Eingabeobjekt gelesen / geschrieben werden muss . Noch stärker bedeutet dies, dass ich beabsichtige, dieses Objekt zu ändern. Diese Funktion hat Nebenwirkungen . Ein const ref impliziert schreibgeschützt , und rvalue ref bedeutet, dass ich die Ressourcen nehmen kann . Wenn Sie
test_1()
am Ende das aufrufen würden,NON-CONST
Konstruktor aufgerufen würde, würde dies bedeuten, dass ich beabsichtige, dieses Objekt zu ändern, obwohl es nach Abschluss nicht mehr existiert.Was (glaube ich) ein Fehler wäre (ich denke an einen Fall, in dem die Bindung einer Referenz während der Initialisierung davon abhängt, ob das übergebene Argument const ist oder nicht).Was mich ein bisschen mehr beschäftigt, ist die Subtilität, die von eingeführt wurde
test_2()
. Hier Kopie-list-Initialisierung stattfindet , statt den Regeln in Bezug auf [class.copy.elision] zitiert oben. Jetzt sagen Sie wirklich, dass Sie ein Objekt vom Typ MyClass zurückgeben, als hätte ich es initialisiertbuf
, sodass dasNON-CONST
Verhalten aufgerufen wird. Ich habe Init-Listen immer als Mittel gesehen, um prägnanter zu sein, aber hier machen die geschweiften Klammern einen signifikanten semantischen Unterschied. Dies wäre wichtiger, wenn die Konstruktoren fürMyClass
eine große Anzahl von Argumenten verwenden würden. Angenommen, Sie möchten ein erstellenbuf
, es ändern und dann mit der großen Anzahl von Argumenten zurückgeben und dasCONST
Verhalten aufrufen . Angenommen, Sie haben die Konstruktoren:Und testen:
Godbolt sagt uns, dass wir
NON-CONST
Verhalten bekommen , obwohlCONST
es wahrscheinlich das ist, was wir wollen (nachdem Sie die Cool-Hilfe zur Funktionsargument-Semantik getrunken haben). Aber jetzt macht die Initialisierung der Kopierliste nicht das, was wir möchten. Der folgende Test macht meinen Standpunkt besser:Um nun die richtige Semantik bei der Initialisierung der Kopierliste zu erhalten, muss der Puffer am Ende "zurückgebunden" werden. Ich denke, wenn das Ziel wäre, dass dieses Objekt ein anderes
MyClass
Objekt initialisiert ,NON-CONST
wäre es in Ordnung , nur das Verhalten in der Rückgabekopierliste zu verwenden, wenn der Verschiebungs- / Kopierkonstruktor das entsprechende Verhalten aufruft, aber das klingt langsam hübsch zart.quelle