Can one change active member in a union with copying of previous active member's value using std::construct_at
?
This minimal example
#include <memory>
constexpr int f() {
union U {
char x{0};
int y;
} u;
std::construct_at(&u.y, u.x);
return u.y;
}
static_assert( f() == 0 );
is accepted by GCC and MSVC, but Clang rejects it with the error:
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__memory/construct_at.h:41:50:
note: read of member 'x' of union with active member 'y' is not allowed in a constant expression
41 | return ::new (std::__voidify(*__location)) _Tp(std::forward<_Args>(__args)...);
Online demo: https://gcc.godbolt.org/z/xrj57jroj
Which implementation is correct here?
The resolution of CWG 2721 clarifies that storage is considered reused by new
when the allocation function returns and before the new
initializer is evaluated.
When the storage is reused, then the lifetime of the previous object ends and it is no longer active.
Therefore Clang is correct. std::construct_at
takes arguments by-reference and forwards them to the new
initializer. The lvalue-to-rvalue conversion is applied to u.x
in order to obtain the stored value only during the evaluation of the initializer. At this point the storage has already been reused and the lifetime of u.x
has ended.
The std::construct_at
call is therefore not permitted in a constant expression and has undefined behavior outside of one.
This can be fixed by writing
std::construct_at(&u.y, auto(u.x));
or
auto v = u.x;
std::construct_at(&u.y, v);
instead.