Recentemente, acompanhei uma discussão no Reddit que levou a uma boa comparação de std::visit
otimização entre compiladores. Notei o seguinte: https://godbolt.org/z/D2Q5ED
O GCC9 e o Clang9 (acho que compartilham o mesmo stdlib) não geram código para verificar e lançar uma exceção sem valor quando todos os tipos atendem a algumas condições. Isso leva a um codegen muito melhor; portanto, levantei um problema com o MSVC STL e fui apresentado com este código:
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&) {}
}
A alegação era de que isso torna qualquer variante sem valor e, lendo o documento , deveria:
Primeiro, destrói o valor atualmente contido (se houver). Inicializa diretamente o valor contido como se estivesse construindo um valor do tipo
T_I
com os argumentosstd::forward<Args>(args)....
Se uma exceção for lançada,*this
pode se tornar valueless_by_exception.
O que não entendo: por que é indicado como "pode"? É legal permanecer no estado antigo se toda a operação for lançada? Porque é isso que o GCC faz:
// 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 mais tarde (condicionalmente) faz algo como:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Portanto, basicamente ele cria um temporário e, se isso for bem sucedido, o copia / move para o lugar real.
Na IMO, esta é uma violação de "Primeiro, destrói o valor atualmente contido", conforme declarado pelo documento. Enquanto leio o padrão, depois de um v.emplace(...)
valor atual na variante é sempre destruído e o novo tipo é o tipo definido ou sem valor.
Eu entendo que a condição is_trivially_copyable
exclui todos os tipos que têm um destruidor observável. Portanto, isso também pode ser feito como: "a variante como se é reinicializada com o valor antigo" ou mais. Mas o estado da variante é um efeito observável. O padrão permite, de fato, queemplace
isso não mude o valor atual?
Edite em resposta a uma cotação padrão:
Em seguida, inicializa o valor contido como se estivesse iniciando diretamente um valor do tipo TI com os argumentos
std::forward<Args>(args)...
.
Será que T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
realmente contam como uma implementação válida do acima? É isso que se entende por "como se"?
might/may
redação, pois o padrão não indica qual é a alternativa.