我正在使用 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
,因为“在常量表达式中不允许对没有活动成员的联合体的成员‘buf_’进行赋值。”
请注意,对于第三个构造函数(上面已注释掉),clang 接受代码,而 gcc(我认为这是明智的)抱怨“'test()' 不是常量表达式,因为它引用了一个未完全初始化的变量。”
哪个编译器是正确的?有其他方法可以实现我的需求吗?在许多情况下,我只需要缓冲区的第一个字节,因此在非
constexpr
上下文中构造对象时不希望填充整个内容的开销。
就其价值而言,我使用的是 gcc 13.2.1 和 clang 16.0.6。
是的,您可以在常量表达式中激活一个简单的数组联合成员,就像在运行时使用 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 */;
Clang 严格遵守此规则,而 GCC 则过于宽松。