Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error in Accessing a Union Member in Constant Expression

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.

like image 568
AJ Tan Avatar asked Nov 07 '22 05:11

AJ Tan


1 Answers

Your constexpr function is invalid before c++20, because it violates the following rule:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

an assignment expression or invocation of an assignment operator ([class.copy]) that would change the active member of a union;

From c++20, this restriction has been clarified here:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

an invocation of an implicitly-defined copy/move constructor or copy/move assignment operator for a union whose active member (if any) is mutable, unless the lifetime of the union object began within the evaluation of E;

Which I believe makes your code valid.

like image 77
cigien Avatar answered Nov 13 '22 18:11

cigien