Fehler C2280 move-ctor deleted und std::push_back
-
@hustbaer sagte in Fehler C2280 move-ctor deleted und std::push_back:
Was den Rest angeht: keine Ahnung ob da Clang und GCC oder MSVC Recht haben.
Ich glaube ich habe mein simples Beispiel von oben fehlinterpretiert. GCC und Clang default-konstuieren in Zeile 10 mit
A a{ A{} };
das Objekt direkt ina
, ohne dass Move und/oder Copy-Konstruktoren berücksichtigt werden und daher auch kein Fehler gemeldet wird.Wenn man das so umformuliert:
A a0; A a{ std::move(a0) };
dann laufen auch GCC und Clang auf einen Fehler.
Der eigentliche Unterschied ist also nicht, dass GCC und Clang in diesem Fall den Copy-Konstruktor verwenden, sondern dass sie eben nur den Default-Konstruktor aufrufen. Ich denke das gehört in diesem Fall sogar zur Mandatory Copy Elision, die vorhandene Copy/Move-Konstruktoren nicht berücksichtigt, womit MSVC in diesem Fall zu übereifrig wäre und hier falsch liegt.
Dass es überhaupt zu einem Fehler kommt finde ich schon etwas unintuitiv, da ja eben noch der Copy-Konstruktor existiert. Aber eine
delete
d-Funktion ist eben "deklariert" und wird tatsächlich für Overload Resolution herangezogen.Zuerst dachte ich, da gäbe es mit dem hier auf cppreference sei die entsprechende Ausnahme:
Additional rules for constructor candidates
Defaulted move constructor and move assignment that are defined as deleted are never included in the list of candidate functions. (since C++11)Die Formulierung fand ich aber recht seltsam. "Defaulted move" die als "deleted" definiert sind. Hä? Entweder "defaulted" oder "deleted", wie soll denn beides gehen?
Die relevante Stelle im Standard (in dieser Version unter 12.4.1 (8)) - verewendet diese ähnliche Formulierung:
A defaulted move special function that is defined as deleted is excluded from the set of candidate functions in all contexts.
und bringt aber auch ein Beispiel, was damit anscheinend gemeint ist:
struct A { A(); // #1 A(A &&); // #2 template<typename T> A(T &&); // #3 }; struct B : A { using A::A; B(const B &); // #4 B(B &&) = default; // #5, implicitly deleted struct X { X(X &&) = delete; } x; }; extern B b1; B b2 = static_cast<B&&>(b1); // calls #4: #1 is not viable, #2, #3, and #5 are not candidates struct C { operator B&&(); }; B b3 = C(); // calls #4
#5 ist so wie ich das verstehe ein
default
Move-Konstruktor der implizitdelete
d ist, da die Klasse einen Member mit einem explizitdelete
d Move-Konstruktor hat. Also "defaulted" und "deleted" gleichzeitig scheint es tatsächlich zu gebenDiese Ausnahme würde tatsächlich eine Workaround erlauben, wenn man es denn wirklich darauf anlegen will, unbedingt einen solchen gelöschten Move zu haben. Man könnte eben dafür sorgen, dass der "defaulted" Move-Konstruktor implizit "deleted" ist:
#include <iostream> #include <utility> struct A { A() = default; A(const A&) { std::cout << "copy A" << std::endl; } A(A&&) = default; struct X { X() = default; X(const X&) = default; X(X&&) = delete; } x; }; int main() { A a0; A a{ std::move(a0) }; }
Und in der Tat kompiliert das auf allen 3 Compilern und gibt jeweils
copy A
aus. Clang findet dieses barocke Konstrukt allerdings gar nicht toll und spuckt eine Warnung aus, man solleA(A&&)
doch bitte explizitdelete
n:
-
@Finnegan
In dem Beispiel kannst du dasA(A&&) = default;
auch genau so gut weglassen, da die Deklaration vonA(const A&)
schon dafür sorgt dassA
keinen move-ctor hat.
-
@hustbaer sagte in Fehler C2280 move-ctor deleted und std::push_back:
@Finnegan
In dem Beispiel kannst du dasA(A&&) = default;
auch genau so gut weglassen, da die Deklaration vonA(const A&)
schon dafür sorgt dassA
keinen move-ctor hat.Ich will ja für das Beispiel gerade einen Move Constructor haben, um ihn implizit löschen zu können. Das Ziel ist ja, den compiler-generierten Move-CTOR so zu löschen, dass er nicht für die Overload Resolution herangezogen wird (und dann wegen "deleted" auf einen Fehler läuft). Wenn erst gar keiner erzeugt wird, dann macht das ganze Beispiel keinen Sinn
Ferner ist
A(const A&)
eh nur für die Debug-Ausgabe um zu sehen, dass der Copy Constructor auch tatsächlich aufgerufen wird und eben nicht aus irgendeinem Grund dochA(A&&)
.Vielleicht ginge das auch noch etwas eleganter und ohne extra Speicher zu belegen mit ner Basisklasse, die ein gelöschtes Move hat (Empty Base Optimization)*.
Dennoch frage ich mich immer noch, welchen Use Case es für sowas geben könnte. Vielleicht ein Member, dessen Klasse ein sinnvolles Move implementiert, das man aber in speziell diesem Kontext (als Member einer bestimmten Klasse) unterbinden möchte (?).
* Edit: Ja, so ists besser, wenn man sowas wirklich ernsthaft nutzen will: https://godbolt.org/z/secahe6rs.
Die Warnungen stören in diesem Fall allerdings etwas, da das ja genau das ist, was erreicht werden soll - die gibts aber eh nur bei dem explizitendefault
.