Pourquoi std :: swap n'est-il pas marqué constexpr avant C ++ 20?

14

En C ++ 20, std::swapdevient une constexprfonction.

Je sais que la bibliothèque standard était vraiment en retard sur le langage pour marquer les choses constexpr, mais en 2017, elle <algorithm>était à peu près constante, tout comme un tas d'autres choses. Pourtant - std::swapne l'était pas. Je me souviens vaguement qu'il y a un étrange défaut de langage qui empêche ce marquage, mais j'oublie les détails.

Quelqu'un peut-il expliquer cela de manière succincte et claire?

Motivation: besoin de comprendre pourquoi ce peut être une mauvaise idée de marquer une std::swap()fonction semblable constexprà du code C ++ 11 / C ++ 14.

einpoklum
la source

Réponses:

11

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 best int).

Étant donné std::swapque les std::invokedeux 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.

Barry
la source
Alors, pourquoi CWG-1581 empêche / rend-il indésirable de marquer une fonction de swap comme constexpr?
einpoklum
3
@einpoklum swap requiert std::is_move_constructible_v<T> && std::is_move_assignable_v<T>is true. Cela ne peut pas se produire si les fonctions membres spéciales ne sont pas encore générées.
NathanOliver
@NathanOliver: J'ai ajouté ceci à ma réponse.
einpoklum
5

La raison

(en raison de @NathanOliver)

Pour autoriser une constexprfonction 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

  • 2016: Antony Polukhin soumet la proposition P0202 , pour marquer toutes les <algorithm>fonctions comme constexpr.
  • Le groupe de travail principal du comité de normalisation discute du défaut CWG-1581 . Ce problème a rendu problématique d'avoir constexpr std::swap()et aussi constexpr std::invoke()- voir l'explication ci-dessus.
  • 2017: Antony révise sa proposition à plusieurs reprises pour exclure std::swapet certaines autres constructions, et cela est accepté dans C ++ 17.
  • 2017: Une résolution pour le problème CWG-1581 est soumise sous la cote P0859 et acceptée par le comité de normalisation en 2017 (mais après l'expédition de C ++ 17).
  • Fin 2017: Antony soumet une proposition complémentaire, P0879 , pour rendre std::swap()constexpr après la résolution du CWG-1581.
  • 2018: La proposition complémentaire est acceptée (?) En C ++ 20. Comme le souligne Barry, le 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 pas std::swap()par ce genre de chose.

einpoklum
la source