Ich habe kürzlich eine Reddit-Diskussion verfolgt, die zu einem schönen Vergleich der std::visit
Optimierung zwischen Compilern führte. Mir ist Folgendes aufgefallen: https://godbolt.org/z/D2Q5ED
Sowohl GCC9 als auch Clang9 (ich denke, sie haben dieselbe stdlib) generieren keinen Code zum Überprüfen und Auslösen einer wertlosen Ausnahme, wenn alle Typen bestimmte Bedingungen erfüllen. Dies führt zu einem viel besseren Codegen, daher habe ich ein Problem mit der MSVC STL angesprochen und wurde mit diesem Code konfrontiert:
template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}
Die Behauptung war, dass dies jede Variante wertlos macht und das Dokument lesen sollte:
Zerstört zunächst den aktuell enthaltenen Wert (falls vorhanden). Initialisiert dann den enthaltenen Wert direkt, als würde ein Wert vom Typ
T_I
mit den Argumenten erstellt.std::forward<Args>(args)....
Wenn eine Ausnahme ausgelöst wird,*this
kann dies zu einer wertlosen Ausnahme führen .
Was ich nicht verstehe: Warum wird es als "Mai" angegeben? Ist es legal, im alten Zustand zu bleiben, wenn die ganze Operation auslöst? Denn genau das macht GCC:
// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };
Und später macht es (bedingt) so etwas wie:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Daher wird im Grunde genommen ein temporäres Objekt erstellt, und wenn dies erfolgreich ist, wird es kopiert / an den realen Ort verschoben.
IMO ist dies eine Verletzung von "Zerstört zuerst den aktuell enthaltenen Wert", wie im Dokument angegeben. Wenn ich den Standard lese, wird nach a v.emplace(...)
der aktuelle Wert in der Variante immer zerstört und der neue Typ ist entweder der eingestellte Typ oder wertlos.
Ich verstehe, dass die Bedingung is_trivially_copyable
alle Typen ausschließt, die einen beobachtbaren Destruktor haben. Dies kann aber auch so lauten: "Als ob die Variante mit dem alten Wert neu initialisiert wird" oder so. Der Zustand der Variante ist jedoch ein beobachtbarer Effekt. Erlaubt der Standard also tatsächlich, dass emplace
sich der aktuelle Wert nicht ändert?
Bearbeiten als Antwort auf ein Standardangebot:
Initialisiert dann den enthaltenen Wert so, als würde ein Wert vom Typ TI mit den Argumenten direkt ohne Listeninitialisierung initialisiert
std::forward<Args>(args)...
.
Gilt das T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
wirklich als gültige Implementierung des oben genannten? Ist das das, was mit "als ob" gemeint ist?
might/may
Wortlaut verwirrt mich sehr, da der Standard nicht angibt, was die Alternative ist.