Recientemente seguí una discusión de Reddit que condujo a una buena comparación de la std::visit
optimización entre los compiladores. Noté lo siguiente: https://godbolt.org/z/D2Q5ED
Tanto GCC9 como Clang9 (supongo que comparten el mismo stdlib) no generan código para verificar y lanzar una excepción sin valor cuando todos los tipos cumplen algunas condiciones. Esto conduce a un mejor codegen, por lo tanto, planteé un problema con el MSVC STL y me presentaron 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&) {}
}
La afirmación era que esto hace que cualquier variante no tenga valor, y al leer el documento debería:
Primero, destruye el valor actualmente contenido (si lo hay). Luego, inicializa directamente el valor contenido como si construyera un valor de tipo
T_I
con los argumentos.std::forward<Args>(args)....
Si se produce una excepción,*this
puede convertirse en valueless_by_exception.
Lo que no entiendo: ¿por qué se dice "puede"? ¿Es legal permanecer en el estado anterior si se produce toda la operación? Porque esto es lo que hace 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>>
{ };
Y luego (condicionalmente) hace algo como:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Por lo tanto, básicamente crea un temporal, y si eso tiene éxito, lo copia / mueve al lugar real.
OMI, esto es una violación de "Primero, destruye el valor actualmente contenido" como se indica en el documento. Mientras leo el estándar, después de un v.emplace(...)
valor actual en la variante siempre se destruye y el nuevo tipo es el tipo establecido o no tiene valor.
Sí entiendo que la condición is_trivially_copyable
excluye todos los tipos que tienen un destructor observable. Entonces, esto también puede ser como: "la variante as-if se reinicializa con el valor anterior" más o menos. Pero el estado de la variante es un efecto observable. Entonces, ¿permite el estándar que eso emplace
no cambie el valor actual?
Edite en respuesta a una cotización estándar:
Luego inicializa el valor contenido como si inicializara sin listar un valor de tipo TI con los argumentos
std::forward<Args>(args)...
.
¿ T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
Realmente cuenta como una implementación válida de lo anterior? ¿Es esto lo que se entiende por "como si"?
might/may
redacción ya que el estándar no establece cuál es la alternativa.