Das seltsame Sprachproblem ist CWG 1581 :
Klausel 15 [special] ist völlig klar, dass spezielle Mitgliedsfunktionen nur implizit definiert werden, wenn sie odr-verwendet werden. Dies schafft ein Problem für konstante Ausdrücke in nicht bewerteten Kontexten:
struct duration {
constexpr duration() {}
constexpr operator int() const { return 0; }
};
// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});
Das Problem hierbei ist, dass wir constexpr duration::duration(duration&&)
in diesem Programm nicht implizit definieren dürfen , sodass der Ausdruck in der Initialisierungsliste kein konstanter Ausdruck ist (da er eine nicht definierte constexpr-Funktion aufruft), sodass der geschweifte Initialisierer eine einschränkende Konvertierung enthält Das Programm ist also schlecht geformt.
Wenn wir Zeile 1 auskommentieren, wird der Verschiebungskonstruktor implizit definiert und das Programm ist gültig. Diese gruselige Aktion aus der Ferne ist äußerst unglücklich. In diesem Punkt gehen die Implementierungen auseinander.
Sie können den Rest der Problembeschreibung lesen.
Eine Lösung für dieses Problem wurde 2017 in P0859 in Albuquerque verabschiedet (nachdem C ++ 17 ausgeliefert wurde). Dieses Problem war ein Blocker für beide, die ein constexpr std::swap
(behoben in P0879 ) und ein constexpr std::invoke
(behoben in P1065 , das auch CWG1581-Beispiele enthält) haben konnten, beide für C ++ 20.
Das meiner Meinung nach am einfachsten zu verstehende Beispiel ist der Code aus dem LLVM-Fehlerbericht, auf den in P1065 hingewiesen wird:
template<typename T>
int f(T x)
{
return x.get();
}
template<typename T>
constexpr int g(T x)
{
return x.get();
}
int main() {
// O.K. The body of `f' is not required.
decltype(f(0)) a;
// Seems to instantiate the body of `g'
// and results in an error.
decltype(g(0)) b;
return 0;
}
CWG1581 dreht sich alles um , wenn constexpr Elementfunktionen definiert sind, und die Auflösung sicherstellt , dass sie nur bei der Verwendung definiert. Nach P0859 ist das Obige gut geformt (die Art von b
ist int
).
Da std::swap
und std::invoke
beide sich auf die Überprüfung auf Elementfunktionen verlassen müssen (Verschieben der Konstruktion / Zuweisung im ersteren und des Anrufbetreibers / Ersatzaufrufs im letzteren), waren beide von der Lösung dieses Problems abhängig.
std::is_move_constructible_v<T> && std::is_move_assignable_v<T>
isttrue
. Dies kann nicht passieren, wenn die speziellen Elementfunktionen noch nicht generiert wurden.Der Grund
(wegen @NathanOliver)
Um eine
constexpr
Swap-Funktion zuzulassen , müssen Sie vor dem Instanziieren der Vorlage für diese Funktion überprüfen, ob der Swap-Typ verschiebbar und beweglich zuweisbar ist. Aufgrund eines Sprachfehlers, der nur in C ++ 20 behoben wurde, können Sie dies leider nicht überprüfen, da die relevanten Elementfunktionen für den Compiler möglicherweise noch nicht definiert wurden.Die Chronologie
<algorithm>
Funktionen als zu kennzeichnenconstexpr
.constexpr std::swap()
und auchconstexpr std::invoke()
- siehe Erklärung oben.std::swap
einige andere Konstrukte auszuschließen , und dies wird in C ++ 17 akzeptiert.std::swap()
nach der Resolution von CWG-1581 constexpr zu machen .std::invoke()
Fix.Ihr spezieller Fall
Sie können das
constexpr
Tauschen verwenden, wenn Sie nicht auf Konstruierbarkeit und Zuweisbarkeit von Verschiebungen prüfen, sondern direkt nach anderen Merkmalen von Typen suchen, die dies insbesondere sicherstellen. zB nur primitive Typen und keine Klassen oder Strukturen. Theoretisch könnten Sie auch auf die Überprüfungen verzichten und sich mit eventuell auftretenden Kompilierungsfehlern und einem flockigen Verhaltenswechsel zwischen Compilern befassen. Ersetzen Sie es auf keinen Fall durch sostd::swap()
etwas.quelle