Considere o seguinte código:
#include <iostream>
#include <string>
union U
{
std::string s;
U()
{
new(&s) std::string;
}
void Set(std::string new_s)
{
s.~basic_string();
new(&s) std::string(std::move(new_s));
}
~U()
{
s.~basic_string();
}
};
int main()
{
U u;
u.Set("foo");
std::cout << u.s << '\n'; // Do I need `std::launder` here?
}
Eu sei que se eu usasse um array de bytes em vez de uma união, eu teria que std::launder(&s)
acessar a string. Mas eu preciso disso aqui?
A resposta do senso comum parece ser "não", mas então onde está a bênção padrão union
de não precisar std::launder
?
A expressão de acesso de membro
u.s
é simplesmente definida para resultar em um lvalue para os
subobjeto membro da união. (Isso é verdadeiro mesmo seu.s
não estiver em seu tempo de vida, ou seja, não estiver ativo.) Veja [expr.ref]/6.2 .Então não pode haver necessidade de lavar. Você já tem um glvalue para o objeto que quer.
Mesmo se você começar de um ponteiro para o objeto de união e tentar usar uma abordagem de conversão de ponteiro, mais semelhante à abordagem de matriz de bytes, ainda funcionará sem
std::launder
, por exemplo:Isso ocorre porque um objeto de união é interconvertível por ponteiro com qualquer um de seus subobjetos membros, conforme [basic.compound]/4.2 , o que significa que ele
reinterpret_cast
produzirá um ponteiro para o subobjeto membro imediatamente, conforme [expr.static.cast]/14 .std::launder
é necessário se o resultado de uma conversão ainda apontar para o objeto original devido à falta de interconversibilidade de ponteiros entre os objetos de origem e de destino.E também, por [basic.object]/2 junto com [basic.life]/8 os objetos que você criar
new
se tornarão subobjetos membros da união es
designarãou
o subobjeto mais recente, porque eles satisfazem todos os requisitos mencionados nesses parágrafos (em particular sobreposição exata coms
e correspondência exata de tipo coms
).Portanto,
s.u
também não se referirá acidentalmente aostd::string
subobjeto anterior que foi substituído por um posteriornew
.