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.