I'm using C++20 and have a structure that contains a transparent union that contains an array. I need my structure to have a constexpr constructor, but I would like to avoid filling the entire array in the event that the constructor is not called in a constexpr context. Gcc allows me to do this, while clang does not.
Here is a minimal example:
#include <algorithm>
#include <type_traits>
struct test {
union {
char buf_[15];
};
// Both gcc and clang accept:
// constexpr test() : buf_{} { fillbuf(); }
// Gcc accepts and clang rejects:
constexpr test() { fillbuf(); }
// Clang accepts and gcc rejects
// constexpr test() {}
constexpr void fillbuf() {
if (std::is_constant_evaluated())
std::fill_n(buf_, sizeof(buf_), 0);
}
};
constinit test mytest{};
With the second constructor (the one I want), clang complains that my constructor is not constexpr because "assignment to member 'buf_' of union with no active member is not allowed in a constant expression."
Note that with the third constructor (commented out above), clang accepts the code while gcc (sensibly, I think) complains that "'test()' is not a constant expression because it refers to an incompletely initialized variable."
Which compiler is right? Is there an alternative way to achieve what I need? In many cases I only need the first byte of the buffer, so don't want the overhead of filling the whole thing when constructing an object in a non-constexpr context.
For what it's worth, I'm using gcc 13.2.1 and clang 16.0.6.
Yes, you can activate a trivial array union member in a constant expression just as normally at runtime with C++20.
The problem is that
can't activate any member. The only form of expression that can implicitly start the lifetime of objects like this is a built-in or trivial assignment expression whose left-hand is a (chain of) built-in member access and array index operations on an expression that nominates a union member. See [class.union.general]/6 for the details.
Because
fill_nwill however assign through a pointer to the union member, i.e. without naming the union member, it does not qualify for that special rule. Therefore it can't start the lifetime of a union member and can't make a union member active, whether at runtime or in a constant expression. Thefill_ncall has undefined behavior at runtime.So, you can easily make the member active before the call to
fill_nyourself by adding:or directly using a loop to assign to
buf_[i](not through a reference or pointer).Clang follows this rule strictly, while GCC is being overly permissive.