您可以在 C++20 constexpr 构造函数中隐式激活联合的数组成员吗?

Can you implicitly activate array member of union in a C++20 constexpr constructor?

提问人:user3188445 提问时间:11/13/2023 最后编辑:user17732522user3188445 更新时间:11/13/2023 访问量:90

问:

我正在使用 C++20 并且有一个包含包含数组的透明联合的结构。我需要我的结构有一个构造函数,但我希望避免在上下文中未调用构造函数的情况下填充整个数组。Gcc 允许我这样做,而 clang 则不然。constexprconstexpr

下面是一个最小的示例:

#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 抱怨我的构造函数不是因为“在常量表达式中不允许将函数分配给没有活动成员的联合的成员'buf_'。constexpr

请注意,对于第三个构造函数(上面注释掉了),clang 接受了代码,而 gcc(我认为是明智的)抱怨“'test()' 不是一个常量表达式,因为它引用了一个未完全初始化的变量。

哪个编译器是正确的?有没有其他方法可以实现我的需求?在许多情况下,我只需要缓冲区的第一个字节,因此在非上下文中构造对象时,不希望填充整个内容的开销。constexpr

值得一提的是,我使用的是 gcc 13.2.1 和 clang 16.0.6。

C++ C++20 constexpr 联合

评论

0赞 Pepijn Kramer 11/13/2023
一般来说,在C++中停止使用它有很多陷阱(活动成员和未定义的行为),它主要是为了向后兼容“C”。请看一下 std::variantunion
0赞 user3188445 11/13/2023
@PepijnKramer 您认为它是如何实现的?如果没有联合,你基本上无法做到这一点(部分原因是严格的别名规则)。std::variant

答:

4赞 user17732522 11/13/2023 #1

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

问题是

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

无法激活任何成员。唯一可以隐式启动对象生存期的表达式形式是内置或微不足道的赋值表达式,其左侧是对指定联合成员的表达式的(链)内置成员访问和数组索引操作。有关详细信息,请参见 [class.union.general]/6

但是,由于将通过指针分配给工会成员,即不命名工会成员,因此它不符合该特殊规则的条件。因此,无论是在运行时还是在常量表达式中,它都无法启动联合成员的生存期,也无法使联合成员处于活动状态。调用在运行时具有未定义的行为。fill_nfill_n

因此,您可以通过添加以下内容,轻松地在呼叫自己之前使成员处于活动状态:fill_n

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

或直接使用循环赋值(而不是通过引用或指针)。buf_[i]

Clang 严格遵守这一规则,而 GCC 则过于宽容。

评论

0赞 Red.Wave 11/13/2023
会起作用吗?这似乎是一种合法的用途,但在编译时可以接受吗?uninitialized_fill
1赞 user17732522 11/13/2023
@Red.Wave 不,它不会。 使用 放置和启动单个数组元素的生存期,但从不启动数组对象的生存期,因此它仍然具有 UB。uninitialized_fillnew
-1赞 Artyer 11/13/2023 #2

[expr.basic.lval]p11

如果程序尝试通过类型与以下类型之一不相似的 glvalue 访问对象的存储值,则行为未定义:

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

因此,应将对象表示形式设置为所有零,而不使活动联合成员。std::fill_n(buf_, sizeof(buf_), 0);*thisbuf_