在C中,联合在任何给定时间只能有零个或一个活动成员,并且C标准提供了一些使成员活动的方法。一种方法是在像u.x = 3;
这样的语句中直接赋值。在constexpr
或consteval
上下文中使用联合时,需要遵守这些规则,因为在这些情况下,编译器将拒绝违反对象生存期的代码。
但是,如果我们想通过指向成员的语法激活一个联合成员,使用像u.*member = 3;
这样的语句怎么办?我创建了这个示例C++20程序来测试MSVC 19,Clang 16和GCC 13:
union U
{
int x;
int y;
};
template<auto member = &U::x>
constexpr void activate(U& u) noexcept
{
u.*member = 3;
}
constexpr int get(U& u) noexcept
{
return u.x;
}
constexpr int test() noexcept
{
U u;
activate(u);
return get(u);
}
static constexpr int const result{test()};
int main()
{
return result;
}
在compiler explorer上,MSVC 19和GCC 13似乎都接受程序并为其生成正确的汇编。然而,Clang 16用这个令人困惑的错误拒绝了它:
<source>:22:28: error: constexpr variable 'result' must be initialized by a constant expression
static constexpr int const result{test()};
^ ~~~~~~~~
<source>:10:12: note: assignment to member 'x' of union with no active member is not allowed in a constant expression
u.*member = 3;
^
<source>:19:2: note: in call to 'activate(u)'
activate(u);
^
<source>:22:35: note: in call to 'test()'
static constexpr int const result{test()};
^
1 error generated.
同样值得注意的是,如果我们在模板参数中将&U::x
更改为&U::y
,MSVC 19将错误诊断为由于激活了错误的联合成员,但GCC 13仍然接受程序并生成相同的汇编输出。如果我将y
的类型从int
更改为char
,则GCC 13会像MSVC 19一样诊断问题。无论如何,Clang 16似乎总是对指向成员的指针语法不满意。当不在constexpr
或consteval
上下文中时,所有三个编译器都接受代码的所有变体,似乎constexpr
计算是编译器开始不同的地方。
据我所知,只有MSVC或GCC的行为是正确的,而Clang没有正确处理指向成员的指针语法。但这准确吗?或者C++标准没有任何规定,可以用指向成员的语法来激活联合成员?
简单地说,如果有编译器的话,那么哪个编译器是正确的?
1条答案
按热度按时间cngwdvgl1#
激活联合对象的成员实质上等同于启动成员子对象的生存期。一个对象的生存期可以显式地从一个放置位置-
new
开始,或者在某些条件下(例如:例如,隐含定义的复制/移动构造函数和并集的赋值运算符或类似memcpy
的函数)。此外,还有一个特殊的联合规定,允许在某些条件下使用简单的赋值表达式开始子对象的生存期,这通常没有这种效果。这是在[class]中指定的。6.联合国。正如你在C++20后标准草案N4868的段落链接文本中所看到的,只考虑左操作数纯粹由内置数组下标运算符和
.
成员访问形成的赋值表达式。这意味着通过指向成员的指针(或任何类型的指针或引用)进行访问不能导致赋值表达式隐式地开始子对象的生存期,因此不能更改联合对象的活动成员。
Clang将其作为核心语言未定义的行为拒绝在明显的常量计算表达式中是正确的。Clang对常量表达式中的UB要求更严格,我的印象是,GCC尤其经常接受常量表达式中的UB,而常量表达式或多或少都是UB,这仅仅是因为标准中的限制性措辞。