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 initialisiert buf
, sodass das NON-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ür MyClass
eine große Anzahl von Argumenten verwenden würden. Angenommen, Sie möchten ein erstellen buf
, es ändern und dann mit der großen Anzahl von Argumenten zurückgeben und das CONST
Verhalten aufrufen . Angenommen, Sie haben die Konstruktoren:
template <size_t N>
MyClass(const char (&value)[N], int)
{
std::cout << "CONST int " << value << '\n';
}
template <size_t N>
MyClass(char (&value)[N], int)
{
std::cout << "NON-CONST int " << value << '\n';
}
Und testen:
MyClass test_0() {
char buf[30] = "test_0";
return {buf,0};
}
Godbolt sagt uns, dass wir NON-CONST
Verhalten bekommen , obwohl CONST
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:
MyClass test_0() {
char buf[30] = "test_0";
buf[0] = 'T';
const char (&bufR)[30]{buf};
return {bufR,0};
}
// OUTPUT: CONST int Test_0
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.