Le problème de langue étrange est CWG 1581 :
L'article 15 [spécial] est parfaitement clair que les fonctions membres spéciales ne sont définies implicitement que lorsqu'elles sont utilisées. Cela crée un problème pour les expressions constantes dans des contextes non évalués:
struct duration {
constexpr duration() {}
constexpr operator int() const { return 0; }
};
// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});
Le problème ici est que nous ne sommes pas autorisés à définir implicitement constexpr duration::duration(duration&&)
dans ce programme, donc l'expression dans la liste d'initialisation n'est pas une expression constante (car elle appelle une fonction constexpr qui n'a pas été définie), donc l'initialiseur contreventé contient une conversion rétrécie , donc le programme est mal formé.
Si nous décommentons la ligne n ° 1, le constructeur de déplacement est implicitement défini et le programme est valide. Cette action effrayante à distance est extrêmement regrettable. Les implémentations divergent sur ce point.
Vous pouvez lire le reste de la description du problème.
Une résolution pour ce problème a été adoptée en P0859 à Albuquerque en 2017 (après l'expédition de C ++ 17). Ce problème était un bloqueur pour à la fois avoir un constexpr std::swap
(résolu dans P0879 ) et un constexpr std::invoke
(résolu dans P1065 , qui contient également des exemples CWG1581), tous deux pour C ++ 20.
L'exemple le plus simple à comprendre ici, à mon avis, est le code du rapport de bogue LLVM indiqué dans P1065:
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 est tout quand constexpr fonctions membres sont définies, et la résolution assure qu'ils sont définis uniquement lorsqu'ils sont utilisés. Après P0859, ce qui précède est bien formé (le type de b
est int
).
Étant donné std::swap
que les std::invoke
deux doivent s'appuyer sur la vérification des fonctions des membres (construction / affectation de déplacement dans le premier et opérateur d'appel / appels de substitution dans le second), ils dépendent tous deux de la résolution de ce problème.
std::is_move_constructible_v<T> && std::is_move_assignable_v<T>
istrue
. Cela ne peut pas se produire si les fonctions membres spéciales ne sont pas encore générées.La raison
(en raison de @NathanOliver)
Pour autoriser une
constexpr
fonction d'échange, vous devez vérifier - avant d'instancier le modèle de cette fonction - que le type échangé est constructible et affectable par déplacement. Malheureusement, en raison d'un défaut de langage résolu uniquement en C ++ 20, vous ne pouvez pas vérifier cela, car les fonctions membres pertinentes n'ont peut-être pas encore été définies, en ce qui concerne le compilateur.La chronologie
<algorithm>
fonctions commeconstexpr
.constexpr std::swap()
et aussiconstexpr std::invoke()
- voir l'explication ci-dessus.std::swap
et certaines autres constructions, et cela est accepté dans C ++ 17.std::swap()
constexpr après la résolution du CWG-1581.std::invoke()
correctif constexpr l'est également .Votre cas spécifique
Vous pouvez utiliser l'
constexpr
échange si vous ne vérifiez pas la constructibilité et l'attribution des mouvements, mais vérifiez plutôt directement une autre caractéristique des types qui garantit cela en particulier. par exemple, seuls les types primitifs et aucune classe ou structure. Ou, théoriquement, vous pourriez renoncer aux vérifications et simplement traiter les erreurs de compilation que vous pourriez rencontrer, et avec un changement de comportement instable entre les compilateurs. Dans tous les cas, ne remplacez passtd::swap()
par ce genre de chose.la source