it-swarm.com.de

MSVC und constexpr für Funktionsparameter?

Dieser Code lässt sich gut mit clang und gcc kompilieren.

template<size_t n>
struct N {
    static constexpr size_t v = n;
};

template<size_t n>
constexpr bool operator<(N<n>, size_t n2) {
    return n < n2;
}

template<typename N>
constexpr void foo(N v) {
    static_assert(v < 5);
}

int main()
{
    foo(N<3>{});
    return 0;
}

Wenn ich jedoch MSVC verwende, erhalte ich die Fehlermeldung, dass v < 5 kein konstanter Ausdruck ist. Ich kann verstehen, warum MSVC das denkt, aber ich denke, es ist falsch und clang/gcc sind richtig. Ist es ein Fehler von MSVC?

9
Antoine Morrier

Ja, MSVC ist hier falsch.

Es mag kontraintuitiv erscheinen, dass der Code wohlgeformt ist, denn wie kann v, was kein konstanter Ausdruck ist, möglicherweise in einem konstanten Ausdruck verwendet werden?

Warum ist es erlaubt? Betrachten Sie zunächst, dass ein Ausdruck informell gesehen kein konstanter Ausdruck ist, wenn er zu einem gl-Wert ausgewertet wird, der selbst kein konstanter Ausdruck oder eine Variable ist, die außerhalb des einschließenden Ausdrucks ( [expr.const] p2) ihren Betrieb aufgenommen hat. 7 ).

Zweitens ist operator<constexpr.

Was nun passiert ist, dass v < 5 ein gültiger konstanter Ausdruck ist. Um das zu verstehen, gehen wir die Auswertung des Ausdrucks durch.

Wir haben:

  1. v < 5 ruft Ihre constexpr auf operator<
  2. Die beiden Parameter werden kopiert (beide Literale und keine werden zu einem Nicht-Constexpr-Objekt ausgewertet).
  3. n2 hat sein Leben in der Evaluierung von v < 5 begonnen und ist ein Literal
  4. n ist ein nicht typisierter Vorlagenparameter, der als solcher in einem konstanten Ausdruck verwendet werden kann
  5. Schließlich ruft n < n2 einen eingebauten Operator auf.

Alle diese Regeln verletzen keinen der Punkte in [expr.const] p2 , sodass der resultierende Ausdruck tatsächlich ein konstanter Ausdruck ist, der als Argument für static_assert verwendet werden kann.

Diese Arten von Ausdrücken werden als konvertierte konstante Ausdrücke bezeichnet.

Hier ist ein vereinfachtes Beispiel:

struct Foo {
  constexpr operator bool() { return true; }
};

int main() {
  Foo f;
  static_assert(f);
}
5
Rakete1111

MSVC ist hier falsch. Beginnen wir mit einer vereinfachten Version des Codes:

struct N {
    static constexpr size_t v = 0;
};

constexpr 
  bool operator<(N n1, size_t n2) {
    return n1.v < n2;
}

  void foo(N v) {
    static_assert(v < 5, ""); // C++11 does not allow terse form
}

Wir werden uns zuerst den static_assert ansehen. Haben wir gegen Regeln für konstante Ausdrücke verstoßen? Wenn wir uns [expr.const] p2.2 ansehen:

ein Aufruf einer anderen Funktion als eines constexpr-Konstruktors für eine Literalklasse oder eine constexpr-Funktion [Hinweis: Die Überladungsauflösung (13.3) wird wie gewohnt angewendet - Anmerkung beenden];

Wir sind gut, operator< ist eine Constexpr-Funktion und der Kopierkonstruktor für N ist ein Constexpr-Konstruktor für eine Literalklasse.

Gehen Sie zu operator< und untersuchen Sie den n1.v < n2-Vergleich, und schauen Sie sich [expr.const] p2.9 an:

eine 1-Wert-zu-R-Wert-Umwandlung (4.1), sofern sie nicht auf angewendet wird
- ein Glvalue vom Typ Integral oder Aufzählung, der sich auf ein nichtflüchtiges const-Objekt mit einer vorhergehenden Initialisierung bezieht, das mit einem konstanten Ausdruck initialisiert wurde, oder
- ein wörtlicher Wert, der sich auf ein mit constexpr definiertes nichtflüchtiges Objekt oder auf ein Unterobjekt eines solchen Objekts bezieht, oder
- Ein wörtlicher Wert, der sich auf ein nichtflüchtiges temporäres Objekt bezieht, dessen Lebensdauer noch nicht abgelaufen ist und mit einem konstanten Ausdruck initialisiert wird

Wir sind auch hier gut. Im ursprünglichen Beispiel beziehen wir uns auf ein Argument ohne Typvorlage, das in einem konstanten Ausdruck verwendet werden kann, sodass die gleiche Argumentation auch für diesen Fall gilt. Beide Operanden von < sind verwendbare konstante Ausdrücke.

Wir können auch sehen, dass MSVC den vereinfachten Fall immer noch als schlecht formuliert behandelt obwohl clang und gcc ihn akzeptieren.

0
Shafik Yaghmour