Warum ist std :: swap nicht vor C ++ 20 als constexpr markiert?

14

In C ++ 20 std::swapwird eine constexprFunktion.

Ich weiß, dass die Standardbibliothek beim Markieren von Dingen wirklich hinter der Sprache zurückgeblieben ist constexpr, aber bis 2017 <algorithm>war sie ziemlich genau wie eine Reihe anderer Dinge. Noch std::swapnicht. Ich erinnere mich vage an einen seltsamen Sprachfehler, der diese Markierung verhindert, aber ich vergesse die Details.

Kann jemand dies kurz und klar erklären?

Motivation: Sie müssen verstehen, warum es möglicherweise eine schlechte Idee ist, eine std::swap()ähnliche Funktion constexprin C ++ 11 / C ++ 14-Code zu markieren .

einpoklum
quelle

Antworten:

11

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 bist int).

Da std::swapund std::invokebeide 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.

Barry
quelle
Warum verhindert / macht CWG-1581 es unerwünscht, eine Swap-Funktion als constexpr zu markieren?
Einpoklum
3
@einpoklum Swap erfordert std::is_move_constructible_v<T> && std::is_move_assignable_v<T>ist true. Dies kann nicht passieren, wenn die speziellen Elementfunktionen noch nicht generiert wurden.
NathanOliver
@ NathanOliver: Fügte dies zu meiner Antwort hinzu.
Einpoklum
5

Der Grund

(wegen @NathanOliver)

Um eine constexprSwap-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

  • 2016: Antony Polukhin unterbreitet den Vorschlag P0202 , alle <algorithm>Funktionen als zu kennzeichnen constexpr.
  • Die Kernarbeitsgruppe des Standardausschusses erörtert den Defekt CWG-1581 . Dieses Problem machte es problematisch zu haben constexpr std::swap()und auch constexpr std::invoke()- siehe Erklärung oben.
  • 2017: Antony überarbeitet seinen Vorschlag einige Male, um std::swapeinige andere Konstrukte auszuschließen , und dies wird in C ++ 17 akzeptiert.
  • 2017: Eine Entschließung zur Ausgabe von CWG-1581 wird als P0859 eingereicht und 2017 vom Standardausschuss akzeptiert (jedoch nach Versand von C ++ 17).
  • Ende 2017: Antony unterbreitet einen ergänzenden Vorschlag, P0879 , um std::swap()nach der Resolution von CWG-1581 constexpr zu machen .
  • 2018: Der ergänzende Vorschlag wird in C ++ 20 übernommen (?). Wie Barry betont, ist dies auch der Constexpr- std::invoke()Fix.

Ihr spezieller Fall

Sie können das constexprTauschen 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 so std::swap()etwas.

einpoklum
quelle