In C++, unions can only have zero or one active members at any given time, and the C++ standard provides few ways to make a member active. One such way is by direct assignment in a statement like u.x = 3;
. Respecting these rules is required for working with unions in a constexpr
or consteval
context, as the compiler will reject code that violates object lifetimes in these cases.
But what if we want to active a union member by pointer-to-member syntax, with a statement like u.*member = 3;
? I created this sample C++20 program to test with MSVC 19, Clang 16, and GCC 13:
union U
{
int x;
int y;
};
template<auto member = &U::x>
constexpr void activate(U& u) noexcept
{
u.*member = 3;
}
constexpr int get(U& u) noexcept
{
return u.x;
}
constexpr int test() noexcept
{
U u;
activate(u);
return get(u);
}
static constexpr int const result{test()};
int main()
{
return result;
}
On compiler explorer it appears that MSVC 19 and GCC 13 both accept the program and generate correct assembly for it. However, Clang 16 rejects it with this confusing error:
<source>:22:28: error: constexpr variable 'result' must be initialized by a constant expression
static constexpr int const result{test()};
^ ~~~~~~~~
<source>:10:12: note: assignment to member 'x' of union with no active member is not allowed in a constant expression
u.*member = 3;
^
<source>:19:2: note: in call to 'activate(u)'
activate(u);
^
<source>:22:35: note: in call to 'test()'
static constexpr int const result{test()};
^
1 error generated.
Also interesting to note, if we change &U::x
to &U::y
in the template parameter, MSVC 19 diagnoses the error as being due to the wrong union member being activated, but GCC 13 still accepts the program anyway and generates the same assembly output. If I change the type of y
from int
to char
, then GCC 13 does diagnose the issue like MSVC 19 does. In any case, Clang 16 always seems unhappy with the pointer-to-member syntax. All three compilers accept all variations of the code when not in a constexpr
or consteval
context, it seems that the constexpr
evaluation is where the compilers start to differ.
From my understanding, only MSVC or GCC is behaving correctly, while Clang is not handling the pointer-to-member syntax correctly. But is this accurate? Or does the C++ standard not have any provision for activating a union member with pointer-to-member syntax?
To put things simply, which compiler is correct here, if any?