c++ c++17 optional unions

c++ - ¿Por qué hay un miembro de unión ficticio en algunas implementaciones de std:: opcional?



c++17 optional (1)

Tanto libstdc ++ (GNU) como libc ++ (LLVM) implementan el almacenamiento de valores std::optional usando una unión y ambos incluyen un miembro ficticio.

Implementación de GNU:

using _Stored_type = remove_const_t<_Tp>; struct _Empty_byte { }; union { _Empty_byte _M_empty; _Stored_type _M_payload; };

Implementación de LLVM:

union { char __null_state_; value_type __val_; };

Mi pregunta es: ¿Por qué necesitamos estos _M_empty / __null_state_ members? ¿Hay algo malo con un sindicato de un solo miembro?


El sindicato de un solo miembro crea todo tipo de problemas al intentar ser construible por defecto y ser compatible con constexpr.

Considera este código:

struct nontrivial { constexpr nontrivial(int o) : u{o} {} int u; }; union storage { nontrivial nt; }; struct optional { storage s; }; constexpr auto run() -> int { optional o; return o.s.nt.u; } int main() { constexpr int t = run(); }

Esto está mal formado porque optional tiene un constructor eliminado.

Entonces, una solución simple sería agregar un constructor que inicialice ningún miembro de la unión:

union storage { constexpr storage() {} // standard says no nontrivial nt; };

Pero no funcionará. Los sindicatos Constexpr deben tener al menos un miembro activo. No puede ser una unión vacía. Para solucionar esta limitación, se agrega un miembro ficticio. Esto hace que std::optional utilizable en el contexto constexpr.

(¡Gracias @Barry!) De [dcl.constexpr]/4 (énfasis mío):

La definición de un constructor constexpr cuyo cuerpo de función no es = eliminar cumplirá adicionalmente los siguientes requisitos:

  • si la clase es una unión que tiene miembros variantes ([class.union]), se inicializará exactamente uno de ellos;
  • si la clase es una clase similar a la unión, pero no es una unión, para cada uno de sus miembros de unión anónimos que tengan miembros variantes, se inicializará exactamente uno de ellos;

  • para un constructor no delegante, cada constructor seleccionado para inicializar miembros de datos no estáticos y subobjetos de clase base será un constructor constexpr;

  • para un constructor delegante, el constructor de destino será un constructor constexpr.