c++language-lawyerc++20unionconstexpr

Changing active member in a union with copying using std::construct_at


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?


Solution

  • 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.