wake-up-neo.com

std :: variantenmodifikation in constexpr

Betrachten Sie die folgenden zwei Programme:

#include<variant>
#include<iostream>

constexpr auto f() {
    using T = std::variant<bool, int>;
    T t(false);
    t = T(true);
    return std::get<bool>(t);
}

template<auto V> 
void print() { std::cout << V << "\n"; }

int main() {
    print<f()>();
}

und

#include<variant>
#include<iostream>

constexpr auto f() {
    using T = std::variant<bool, int>;
    T t(false);
    t = T(42);
    return std::get<int>(t);
}

template<auto V> 
void print() { std::cout << V << "\n"; }

int main() {
    print<f()>();
}

GCC stellt beide zusammen und gibt die erwarteten Ergebnisse aus. Clang kompiliert keine davon mit der folgenden Fehlermeldung in beiden Fällen:

<source>:4:16: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
constexpr auto f() {
               ^
<source>:7:7: note: non-constexpr function 'operator=' cannot be used in a constant expression
    t = T(42);
      ^
/opt/compiler-Explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/variant:1095:16: note: declared here
      variant& operator=(variant&&) = default;

Sind die beiden Programme gut ausgebildet? Wenn nein warum?

Auch wenn sie nicht wohlgeformt sind, ist die Fehlermeldung, die Clang gibt, angemessen? Gemäß [variant.assign] sollte der Verschiebungszuweisungsoperator constexpr sein.

Gemäß (7.4) sollte sich die Zuordnung im zweiten Beispiel äquivalent zu emplace<int>(...) verhalten, das nicht als constexpr ( [variant.mod] ) deklariert ist. Bedeutet dies, dass das zweite Beispiel falsch formuliert ist, weil das Vorlagenargument nicht als konstanter Ausdruck ausgewertet werden kann oder der Wortlaut dieses Verhalten zulässt/erfordert?

BEARBEITEN:

Basierend auf den Kommentaren scheint es, dass Clang die korrekten Ergebnisse kompiliert und ausgibt, wenn libc ++ verwendet wird und der Fehler nur bei libstdc ++ auftritt. Ist das eine Inkompatibilität zwischen der Standardbibliothek und dem Compiler?

Auf https://godbolt.org/ :

Funktioniert in beiden Fällen:

  • GCC 8.2.0 -std = c ++ 17
  • Clang 7.0.0 -std = c ++ 17 -stdlib = libc ++

Funktioniert in keinem Fall:

  • Clang 7.0.0 -std = c ++ 17
7
user10605163

Das sieht aus wie ein Clang-Bug wir können aus dem libstdc ++ - Variantenkopf sehen dass der Move-Zuweisungsoperator tatsächlich nicht als constexpr markiert ist:

variant& operator=(variant&&) = default;

ein vorgegebener und implizit definierter Verschiebungszuweisungsoperator kann jedoch immer noch constexpr sein. Dies ist aus [class.copy.assign] p10 (Hervorhebung meiner) ersichtlich:

Ein Zuweisungsoperator für Kopieren/Verschieben für eine Klasse X, die standardmäßig eingestellt ist, und nicht als gelöscht definiert ist implizit definiert, wenn es von odr verwendet wird ([basic.def.odr]) (z. B. wenn es durch Überladungsauflösung ausgewählt wird, um ein Objekt seines Klassentyps zuzuordnen.), wenn es für die Konstante .__ benötigt wird. Auswertung ([expr.const]) oder wenn nach .__ explizit der Standardwert ist. seine erste Erklärung. Die implizit definierte Zuweisung von Kopieren/Verschieben Operator ist constexpr, wenn

  • (10.1) X ist ein Literaltyp und 
  • (10.2) Der Zuweisungsoperator, der zum Kopieren/Verschieben jedes Unterobjekts der direkten Basisklasse ausgewählt wird, ist eine Constexpr-Funktion und 
  • (10.3) Für jedes nicht statische Datenelement von X, das dem Klassentyp (oder einem Array davon) angehört, wird der Zuweisungsoperator zum Kopieren/Verschieben dieses .__ ausgewählt. Member ist eine Constexpr-Funktion.

Soweit ich sagen kann, dass die libstdc ++ - Implementierung in all diese Fälle passen sollte, handelt es sich um einen Literaltyp, es hat keine nicht statischen Datenelemente und der Zuweisungsoperator für alle seine Basen sollte ebenfalls constexpr sein.

2
Shafik Yaghmour