你能在C++20 constexpr构造函数中隐式激活union的数组成员吗?

gupuwyp2  于 11个月前  发布在  其他
关注(0)|答案(2)|浏览(95)

我使用的是C++20,有一个结构体,它包含一个包含数组的透明联合。我需要我的结构体有一个constexpr构造函数,但我希望避免在constexpr上下文中没有调用构造函数的情况下填充整个数组。GCC允许我这样做,而clang不允许。
下面是一个简单的例子:

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

字符串
对于第二个构造函数(我想要的构造函数),clang抱怨我的构造函数不是constexpr,因为“在常量表达式中不允许对没有活动成员的union的成员'buf_'赋值”。
请注意,对于第三个构造函数(上面已注解),clang接受代码,而gcc(我认为这是明智的)抱怨"'test()'不是常量表达式,因为它引用了一个未完全初始化的变量”。
哪个编译器是正确的?在很多情况下,我只需要缓冲区的第一个字节,所以当在非constexpr上下文中构造对象时,我不希望有填充整个缓冲区的开销。
值得一提的是,我使用的是gcc 13.2.1和clang 16.0.6。

t30tvxxf

t30tvxxf1#

是的,你可以在常量表达式中激活一个普通的数组联合成员,就像在C++20运行时一样。
问题是

std::fill_n(buf_, sizeof(buf_), 0);

字符串
不能激活任何成员。唯一可以像这样隐式启动对象生存期的表达式形式是一个内置或普通的赋值表达式,它的左边是一个指定联合成员的表达式上的内置成员访问和数组索引操作(链)。有关详细信息,请参阅[class.union.general]/6。
然而,由于fill_n将通过一个指向联合成员的指针进行赋值,即不命名联合成员,因此它不符合该特殊规则。因此,无论是在运行时还是在常量表达式中,它都不能启动联合成员的生存期,也不能使联合成员处于活动状态。fill_n调用在运行时具有未定义的行为。
因此,您可以在调用fill_n之前轻松地激活成员,方法是添加:

buf_[0] = 0 /* or anything else */;


或者直接使用循环来赋值给buf_[i](不通过引用或指针)。
Clang严格遵守这一规则,而GCC则过于宽容。

jei2mxaa

jei2mxaa2#

[expr.basic.lval]p11:
如果一个程序试图通过一个glvalue来访问一个对象的存储值,而这个glvalue的类型不类似于以下类型之一,那么这个行为是未定义的:

  • 对象的动态类型,
  • 一个类型,它是与对象的动态类型相对应的有符号或无符号类型,或者
  • charunsigned charstd::byte类型。

因此,std::fill_n(buf_, sizeof(buf_), 0);应该将*this的对象表示设置为全零,而不使buf_成为活动联合成员。

相关问题