I was doing some experiments with unions when I encountered a problem.
union U
{
// struct flag for reverse-initialization of each byte
struct rinit_t { };
constexpr static const rinit_t rinit{};
uint32_t dword;
uint8_t byte[4];
constexpr U() noexcept : dword{} { }
constexpr U(uint32_t x) noexcept : dword{x} { }
constexpr U(uint32_t x, const rinit_t&) noexcept : dword{}
{
U temp{x};
byte[0] = temp.byte[3];
byte[1] = temp.byte[2];
byte[2] = temp.byte[1];
byte[3] = temp.byte[0];
}
};
This is my sample instance:
constexpr U x{0x12345678, U::rinit};
I got this error in versions 5.1 and 8.1 of g++ with -std=c++14
, -std=c++17
and -std=c++2a
:
accessing 'U::byte' member instead of initialized 'U::dword' member in constant expression
Accessing and assigning an element of member byte
, whether it's from temp
or this
, reproduces the error. It seems that byte
is recognized by the compiler as an "uninitialized" member even though byte
and dword
share the same address.
I once modified the second constructor:
constexpr U(uint32_t x) noexcept :
byte{uint8_t(x), uint8_t(x >> 8), uint8_t(x >> 16), uint8_t(x >> 24)}
{ }
But I reverted after ending up producing what seems to be a compiler bug:
main.cpp:73:37: internal compiler error: in complete_ctor_at_level_p, at expr.c:5844
constexpr U x{0x12345678, U::rinit};
^
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://tdm-gcc.tdragon.net/bugs> for instructions.
For my current fix, I added a converter:
// converts the value of a uint32_t to big endian format
constexpr static uint32_t uint32_to_be(uint32_t x)
{
return ( (x >> 24) & 0xFF) |
( (x << 8) & 0xFF0000) |
( (x >> 8) & 0xFF00) |
( (x << 24) & 0xFF000000);
}
And I modified the third constructor:
constexpr U(uint32_t x, const rinit_t&) noexcept : dword{uint32_to_be(x)} { }
I'm just curious as to why I'm getting the error. Can anyone help me understand this problem?
Update:
Based on my recent tests, in a constexpr
union
constructor, I can't use a non-static data member that is not in the initialization list. As a result, I added some struct
flags to explicitly specify the initialization of a certain non-static data member.
// struct flag to explicitly specify initialization of U::dword
struct init_dword_t { };
constexpr static const init_dword_t init_dword{};
// struct flag to explicitly specify initialization of U::byte
struct init_byte_t { };
constexpr static const init_byte_t init_byte{};
And with it, I also added new constructors for such kind of initialization. Here are some examples:
constexpr U(const init_byte_t&) noexcept : byte{} { }
// for some reason, this version does not reproduce the internal compiler error
constexpr U(uint32_t x, const init_byte_t&) noexcept :
byte{uint8_t(x), uint8_t(x >> 8), uint8_t(x >> 16), uint8_t(x >> 24)}
{ }
constexpr U(uint32_t x, const init_byte_t&, const rinit_t&) noexcept :
byte{uint8_t(x >> 24), uint8_t(x >> 16), uint8_t(x >> 8), uint8_t(x)}
{ }
It would be great if someone can provide a better solution to this.
-std=c++20
. I'm not sure if this is allowed by the standard, though. – Trichloride