Error in Accessing a Union Member in Constant Expression
Asked Answered
L

2

9

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.

Lindsley answered 5/9, 2020 at 11:11 Comment(3)
g++ 10 and clang 10 seem to accept your alternative second constructor with -std=c++20. I'm not sure if this is allowed by the standard, though.Trichloride
@lajoh90686: Can you please explain why I should use a copy instead of a const reference?Lindsley
Any workaround if I am stuck in C++11?Strophanthus
R
2

In the last constructor, you create an instance of U and assign x to dword. dword is now the active member of that union. It's undefined behavior to access "byte" if it's not the active member.[0] UB is not allowed in a constant expression.[1]

Simpler example:

union U {
    unsigned int i;
    float f;
};
int main()
{
    const float f1 = U{.i=42}.f; // no error
    constexpr float f2 = U{.i=42}.f; // error
    return 0;
}

Compiler output:

<source>: In function 'int main()':
<source>:8:35: error: accessing 'U::f' member instead of initialized 'U::i' member in constant expression
    8 |     constexpr float f2 = U{.i=42}.f; // error
      |                                   ^
Compiler returned: 1

[0] https://en.cppreference.com/w/cpp/language/union

[1] https://en.cppreference.com/w/cpp/language/constant_expression

Revalue answered 26/8, 2022 at 2:1 Comment(0)
C
1

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.

Chaworth answered 5/9, 2020 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.