J'ai récemment suivi une discussion sur Reddit qui a conduit à une belle comparaison de l' std::visit
optimisation entre les compilateurs. J'ai remarqué ce qui suit: https://godbolt.org/z/D2Q5ED
GCC9 et Clang9 (je suppose qu'ils partagent le même stdlib) ne génèrent pas de code pour vérifier et lever une exception sans valeur lorsque tous les types remplissent certaines conditions. Cela conduit à un meilleur codegen, donc j'ai soulevé un problème avec le MSVC STL et j'ai été présenté avec ce code:
template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}
L'affirmation était que cela rend toute variante sans valeur, et la lecture du docu devrait:
Tout d'abord, détruit la valeur actuellement contenue (le cas échéant). Puis initialise directement la valeur contenue comme si la construction d'une valeur de type
T_I
avec les argumentsstd::forward<Args>(args)....
Si une exception est levée,*this
peut devenir sans valeur_par_exception.
Ce que je ne comprends pas: pourquoi est-il dit "peut"? Est-il légal de rester dans l'ancien état si toute l'opération se termine? Parce que c'est ce que fait GCC:
// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };
Et plus tard, il fait (conditionnellement) quelque chose comme:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Par conséquent, fondamentalement, il crée un temporaire, et si cela réussit, le copie / le déplace à la place réelle.
OMI, il s'agit d'une violation de "Tout d'abord, détruit la valeur actuellement contenue" comme indiqué par le docu. Comme je l'ai lu la norme, puis après un v.emplace(...)
la valeur actuelle dans la variante est toujours détruite et le nouveau type est soit le type défini ou sans valeur.
Je comprends que la condition is_trivially_copyable
exclut tous les types qui ont un destructeur observable. Donc, cela peut aussi être bien comme: "comme si la variante est réinitialisée avec l'ancienne valeur" ou plus. Mais l'état de la variante est un effet observable. La norme le permet-elle en effet, cela emplace
ne change pas la valeur actuelle?
Modifier en réponse à un devis standard:
Initialise ensuite la valeur contenue comme si l'initialisation directe sans liste d'une valeur de type TI avec les arguments
std::forward<Args>(args)...
.
Est-ce que T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
cela compte vraiment comme une implémentation valide de ce qui précède? Est-ce ce que l'on entend par «comme si»?
might/may
libellé, car la norme ne précise pas quelle est l'alternative.