4 votes

Std::optional expérimental dans une fonction constexpr

Je voudrais utiliser l'idiome optionnel à l'intérieur de ma fonction constexpr pour clarifier facilement si la variable est définie ou non.

Ce que j'ai essayé avec std::experimental::optional:

constexpr bool call()
{
  std::experimental::optional r; 
  r = true; // Erreur
  // Erreur similaire avec :
  // r = std::experimental::optional(true);

  if (!r)
  {
    return false;
  }
  return *r;
}

Je reçois l'erreur : appel à une fonction non-constexpr - donc l'assignation n'est pas possible, car cette opération ne peut pas être constexpr ([Exemple](https://gcc.godbolt.org/#compilers:!((compiler:g510,options:'-std%3Dc%2B%2B14',source:'%23include+%3Cexperimental/optional%3E%0A%0Aconstexpr+bool+call()%0A%7B%0A++std::experimental::optional%3Cbool%3E+r%3B+%0A++r+%3D+true%3B%0A++%0A++if+(!!r)%0A++%7B%0A++++return+false%3B%0A++%7D%0A++return+*r%3B%0A%7D%0A%0Aconstexpr+bool+f+%3D+call()%3B')),filterAsm:(commentOnly:!t,directives:!t,labels:!t),version:3)).

Mais si j'implémente ma propre (très laide, juste pour l'exemple,filterAsm:(commentOnly:!t,directives:!t,labels:!t),version:3)) classe optionnelle, cela fonctionne, car je n'implémente pas l'opérateur d'assignation/constructeur explicitement.

template
struct optional
{
  bool m_Set;
  T m_Data;

  constexpr optional() :
    m_Set(false), m_Data{}
  {
  }

  constexpr optional(T p_Data) :
    m_Set(true), m_Data(p_Data)
  {
  }

  explicit constexpr operator bool()
  {
    return m_Set;
  }

  constexpr T operator *()
  {
    return m_Data;
  }
};

Comment pourrais-je utiliser std::..::optionnel dans le même contexte avec une assignation à l'intérieur de fonctions constexpr ?

4voto

Yuushi Points 10656

Fondamentalement, vous ne pouvez pas. Le problème avec votre implémentation simple est qu'elle nécessite que T soit par défaut constructible - si ce n'est pas le cas, cela ne fonctionnera pas.

Pour contourner cela, la plupart des implémentations utilisent soit un union soit un stockage (aligné de manière appropriée) capable de contenir un T. Si un T vous est passé dans le constructeur, alors tout va bien, vous pouvez l'initialiser directement (donc ce sera constexpr). Cependant, le compromis ici est que lors de l'appel à operator=, copier la valeur peut nécessiter un appel à placement-new, qui ne peut pas être constexpr.

Par exemple, de LLVM:

template ::type, value_type>::value &&
                  is_constructible::value &&
                  is_assignable::value
              >::type
          >
_LIBCPP_INLINE_VISIBILITY
optional&
operator=(_Up&& __v)
{
    if (this->__engaged_)
        this->__val_ = _VSTD::forward<_Up>(__v);
    else
    {
        // La ligne de problème est ci-dessous - non engagé -> besoin d'appeler
        // un placement new avec la valeur passée en paramètre.
        ::new(_VSTD::addressof(this->__val_)) value_type(_VSTD::forward<_Up>(__v));
        this->__engaged_ = true;
    }
    return *this;
}

Quant à la raison pour laquelle placement new n'est pas constexpr, voir ici.

1voto

WhiZTiM Points 3351

Comment pourrais-je utiliser std::..::optional dans le même contexte avec l'assignation à l'intérieur des fonctions constexpr?

std::optional est destiné à contenir une valeur qui peut être présente ou non. Le problème avec l'assignation de std::optional est qu'elle doit détruire l'ancien état (appeler le destructeur de l'objet contenu) s'il y en a un. Et vous ne pouvez pas avoir un destructeur constexpr.

Bien sûr, les types triviaux et entiers ne devraient pas avoir de problème, mais je suppose que la généralisation était de maintenir les choses saines. Cependant, l'assignation aurait pu être rendue constexpr pour les types triviaux. Espérons que cela sera corrigé. D'ici là, vous pouvez dérouler les vôtres. :-)

Même le constructeur de std::optional que vous pensez être constexpr, est en fait sélectivement constexpr (selon que le constructeur de l'objet sélectionné l'est). Sa proposition peut être trouvée ici

1voto

ecatmur Points 64173

Malheureusement, le support constexpr dans std::optional est quelque peu rudimentaire; les fonctions membres activées par constexpr sont juste les constructeurs (vides et engagés), le destructeur et certains observateurs, vous ne pouvez donc pas modifier l'état engagé d'une optionnel.

Cela est dû au fait qu'il n'y aurait aucun moyen de mettre en œuvre l'assignation pour les types non copiables sans utiliser de nouveaux emplacements et la destruction sur place de l'objet contenu, ce qui est illégal dans le contexte constexpr. Il en va de même pour les constructeurs de copie et de déplacement, bien que cela puisse changer avec garantie d'élan de copie, mais en tout cas, la norme marque ces fonctions membres spéciales comme non-constexpr, vous ne pouvez donc pas les utiliser dans le contexte constexpr.

La solution consisterait à rendre l'opérateur d'assignation conditionnellement constexpr dépendant du fait que le type contenu est trivial (std::is_trivial_v).

Il y a une discussion sur ce problème dans l'implémentation de référence; bien qu'il soit probablement trop tard pour obtenir une affectation constexpr pour les optionnels triviaux dans la prochaine version de la norme, rien ne vous empêche d'écrire le vôtre (par exemple, en copiant et en corrigeant l'implémentation de référence).

1voto

Ceci n'est pas possible, comme expliqué dans n3527:

Rendre optional un type littéral

Nous proposons que optional soit un type littéral pour les types T trivialement destructibles.

constexpr optional oi{5};
static_assert(oi, "");            // ok
static_assert(oi != nullopt, ""); // ok
static_assert(oi == oi, "");      // ok
int array[*oi];                   // ok: tableau de taille 5 

Rendre optional un type littéral en général est impossible : le destructeur ne peut pas être trivial car il doit exécuter une opération qui peut être conceptuellement décrite comme :

~optional() {
  if (is_engaged()) destroy_contained_value();
}

Il est toutefois possible de rendre le destructeur trivial pour les types T qui fournissent eux-mêmes un destructeur trivial, et nous savons qu'une implémentation efficace d'un tel optional avec une interface en temps de compilation - excepté pour le constructeur de copie et le constructeur de déplacement - est possible. Par conséquent, nous proposons que pour les types T trivialement destructibles tous les constructeurs de optional, à l'exception des constructeurs de déplacement et de copie, ainsi que les fonctions observatrices soient constexpr. Une ébauche d'implémentation de référence est fournie dans cette proposition.

En d'autres termes, il n'est pas possible d'attribuer une valeur à r même si vous le marquez comme constexpr. Vous devez l'initialiser sur la même ligne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X