Considérez les deux programmes suivants:
#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()>();
}
et
#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 compile ces deux éléments et génère les résultats escomptés. Clang ne les compile pas avec le message d'erreur suivant dans les deux cas:
<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;
Les deux programmes sont-ils bien formés? Sinon pourquoi?
De plus, s'ils ne sont pas bien formés, le message d'erreur que Clang donne est-il approprié? Selon [variant.assign] , l'opérateur d'affectation de déplacement doit être constexpr
.
De plus, conformément à (7.4) , l'affectation du deuxième exemple doit se comporter de manière équivalente à emplace<int>(...)
qui n'est pas déclarée constexpr
( [variant.mod] ). Cela implique-t-il que le deuxième exemple est mal formé, car l'argument de modèle ne peut pas être évalué comme une expression constante ou la formulation autorise-t-elle ce comportement?
MODIFIER:
D'après les commentaires, il semble que Clang compile et renvoie les résultats corrects si libc ++ est utilisé et que l'erreur se produit uniquement avec libstdc ++. Est-ce une incompatibilité entre la bibliothèque standard et le compilateur?
Sur https://godbolt.org/ :
Fonctionne dans les deux cas:
Ne fonctionne pas dans les deux cas:
Cela ressemble à un bogue de clang nous pouvons voir dans l'en-tête de variante de libstdc ++ que l'opérateur d'affectation de déplacement n'est en effet pas marqué constexpr:
variant& operator=(variant&&) = default;
mais un opérateur d’affectation de mouvement par défaut et défini implicitement peut toujours être constexpr, nous pouvons le voir à partir de [class.copy.assign] p10 (emphase mine):
Opérateur d’affectation copie/déplacement pour une classe X par défaut et non défini comme supprimé est défini implicitement lorsque odr-used ([basic.def.odr]) (par exemple, quand il est sélectionné par résolution de surcharge pour affecter à un objet de son type de classe), quand il est nécessaire pour constante evaluation ([expr.const]), ou lorsqu'il est explicitement configuré par défaut après sa première déclaration. L'affectation de copie/déplacement définie implicitement l'opérateur est constexpr si
- (10.1) X est un type littéral, et
- (10.2) l'opérateur d'affectation sélectionné pour copier/déplacer chaque sous-objet de classe de base directe est une fonction constexpr, et
- (10.3) pour chaque membre de données non statique de X qui est de type classe (ou un tableau de celle-ci), l'opérateur d'affectation sélectionné pour copier/déplacer ce membre est une fonction constexpr.
D'après ce que je peux dire, l'implémentation de libstdc ++ devrait convenir à tous ces cas, il s'agit d'un type littéral, elle ne comporte pas de données membres non statiques et l'opérateur d'affectation pour toutes ses bases doit également être constexpr.