Di recente ho seguito una discussione su Reddit che ha portato a un bel confronto di std::visit
ottimizzazione tra i compilatori. Ho notato quanto segue: https://godbolt.org/z/D2Q5ED
Sia GCC9 che Clang9 (immagino condividano lo stesso stdlib) non generano codice per controllare e generare un'eccezione senza valore quando tutti i tipi soddisfano alcune condizioni. Questo porta a una migliore codegen, quindi ho sollevato un problema con MSVC STL e mi è stato presentato questo codice:
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'affermazione era che ciò rende qualsiasi variante senza valore e, leggendo il documento , dovrebbe:
Innanzitutto, distrugge il valore attualmente contenuto (se presente). Quindi inizializza direttamente il valore contenuto come se costruisse un valore di tipo
T_I
con gli argomentistd::forward<Args>(args)....
Se viene generata un'eccezione,*this
può diventare valueeless_by_exception.
Quello che non capisco: perché è indicato come "può"? È legale rimanere nel vecchio stato se l'intera operazione viene lanciata? Perché questo è ciò che fa 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>>
{ };
E più tardi (condizionatamente) fa qualcosa di simile:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Quindi fondamentalmente crea un temporaneo, e se ciò riesce riesce a copiarlo / spostarlo nel luogo reale.
IMO questa è una violazione di "In primo luogo, distrugge il valore attualmente contenuto" come affermato dal docu. Mentre leggo lo standard, dopo un v.emplace(...)
valore corrente nella variante viene sempre distrutto e il nuovo tipo è il tipo impostato o senza valore.
Capisco che la condizione is_trivially_copyable
esclude tutti i tipi che hanno un distruttore osservabile. Quindi questo può anche essere come: "come se la variante è reinizializzata con il vecchio valore" o giù di lì. Ma lo stato della variante è un effetto osservabile. Quindi lo standard permette davvero, che emplace
non cambia il valore corrente?
Modifica in risposta a un preventivo standard:
Quindi inizializza il valore contenuto come se l'inizializzazione diretta-non-elenco di un valore di tipo TI con gli argomenti
std::forward<Args>(args)...
.
Fa T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
davvero conta come una valida attuazione di quanto sopra? È questo che si intende per "come se"?
might/may
formulazione in quanto lo standard non indica quale sia l'alternativa.