在常量表达式中访问联合成员出错。

8

我在尝试使用联合体进行一些实验时遇到了问题。

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];
  }
};

这是我的示例实例:

constexpr U x{0x12345678, U::rinit};

我在 g++ 的版本 5.1 和 8.1 中使用 -std=c++14-std=c++17-std=c++2a 时遇到了这个错误:

accessing 'U::byte' member instead of initialized 'U::dword' member in constant expression

访问和赋值成员byte的元素,无论是来自temp还是this,都会产生错误。似乎编译器将byte识别为“未初始化”的成员,即使bytedword共享相同的地址。

我曾经修改过第二个构造函数:

constexpr U(uint32_t x) noexcept :
  byte{uint8_t(x), uint8_t(x >> 8), uint8_t(x >> 16), uint8_t(x >> 24)}
{ }

但是我在最终生成似乎是编译器错误后进行了还原:

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.

为了解决当前的问题,我添加了一个转换器:

// 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);
}

我修改了第三个构造函数:

constexpr U(uint32_t x, const rinit_t&) noexcept : dword{uint32_to_be(x)} { }

我只是好奇为什么会出现这个错误。有人能帮我理解这个问题吗?

更新:

根据我最近的测试,在 constexprunion 构造函数中,我不能使用不在初始化列表中的非静态数据成员。因此,我添加了一些 struct 标记来明确指定某个非静态数据成员的初始化。

// 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{};

同时,我还添加了用于此类初始化的新构造函数。以下是一些示例:

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)}
{ }

如果有人能提供更好的解决方案,那就太棒了。


g++ 10 和 clang 10 似乎可以使用 -std=c++20 来接受您的替代第二个构造函数。尽管如此,我不确定这是否符合标准。 - chi
@lajoh90686:你可以解释一下为什么我应该使用复制而不是常量引用吗? - AJ Tan
2个回答

1
在最后一个构造函数中,您创建了U的一个实例,并将x分配给dword。现在,dword是该联合体的活动成员。 如果"byte"不是活动成员,则访问它是未定义的行为[0]。常量表达式中不允许使用UB[1]。
更简单的例子:
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;
}

编译器输出:

<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

这个链接是关于C++语言中联合体的说明,联合体是一种特殊的数据类型,在同一个内存空间可以存储不同类型的变量。这篇文章详细介绍了如何定义和使用联合体。

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

这个链接是关于C++语言中常量表达式的说明,常量表达式是在编译时就可以计算得出结果的表达式。这篇文章详细介绍了常量表达式的语法和使用方法。

1

您的constexpr函数在c++20之前是无效的,因为它违反了以下规则

表达式e是核心常量表达式,除非按照抽象机器的规则评估e将评估以下表达式之一:

一个赋值表达式或调用联合体的活动成员的赋值运算符([class.copy]),这将更改联合体的活动成员;

从c++20开始,此限制已在此处进行了澄清:

表达式e是核心常量表达式,除非按照抽象机器的规则评估e将评估以下表达式之一:

对于其活动成员(如果有)是可变的union,隐式定义的复制/移动构造函数或复制/移动赋值运算符的调用,除非联合体对象的生命周期始于E的评估内部;

我相信这使得您的代码有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接