c++ - Por que existe um membro de união fictício em algumas implementações do std:: optional?



c++17 unions (1)

A união de membro único cria todo tipo de problema ao tentar ser construtível por padrão e ser compatível com o constexpr.

Considere 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();
}

Isso está mal formado porque optional possui um construtor excluído.

Uma correção simples seria adicionar um construtor que inicialize nenhum membro da união:

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

Mas não vai funcionar. As uniões constexpr devem ter pelo menos um membro ativo. Não pode ser uma união vazia. Para solucionar essa limitação, um membro fictício é adicionado. Isso torna std::optional utilizável no contexto constexpr.

(Obrigado @ Barry!) De [dcl.constexpr]/4 (ênfase minha):

A definição de um construtor constexpr cujo corpo da função não é = delete também deve atender aos seguintes requisitos:

  • se a classe é uma união com membros variantes ([class.union]), exatamente um deles deve ser inicializado;
  • se a classe for do tipo sindical, mas não for sindical, pois cada um de seus membros anônimos do sindicato possui membros variantes, exatamente um deles deve ser inicializado;

  • para um construtor não delegador, todo construtor selecionado para inicializar membros de dados não estáticos e subobjetos da classe base deve ser um construtor constexpr;

  • para um construtor delegador, o construtor de destino deve ser um construtor constexpr.

A libstdc ++ (GNU) e a libc ++ (LLVM) implementam o armazenamento de valor std::optional usando uma união e os dois incluem um membro fictício.

Implementação GNU:

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

Implementação do LLVM:

union
{
    char __null_state_;
    value_type __val_;
};

Minha pergunta é: Por que precisamos desses membros __null_state_ / __null_state_ ? Há algo de errado com uma união de membro único?





unions