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:
Funktioniert in keinem Fall:
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.