2

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?

1 Answer 1

5

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! And if one replaces std::construct_at(&u.y, u.x); with simple u.y = u.x; then the program is accepted by Clang as well. Does this make it well formed now?
Yes, if = resolves to the built-in assignment operator, as it does here, then the specific expression structure u.y = ... starts the lifetime of u.y given that u is a union and y a member of it. In this case the lifetime effect is sequenced immediately before the side effect of the assignment and after the value computations for both operands, including the implicit lvalue-to-rvalue conversion on the right-hand operand. See eel.is/c++draft/class.union#general-5.sentence-3.
@Fedor One might be able to argue that this only mentions sequencing of the beginning of the lifetime of the new object, not its creation and storage reuse, but I think the intent is clear.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.