gez*_*eza 21 c++ language-lawyer c++20
看这个例子(godbolt):
#include <memory>
union U {
int i[1];
};
constexpr int foo() {
U u;
std::construct_at(u.i, 1);
return u.i[0];
}
constexpr int f = foo();
Run Code Online (Sandbox Code Playgroud)
gcc 和 msvc 成功编译了这个,但 clang 抱怨:
常量表达式中不允许构造没有活动成员的联合体成员“i”的子对象
哪个编译器是正确的?我认为 clang 在这里是错误的,因为 C++20 隐式创建对象(P0593)应该使该程序有效(因为应该隐式创建数组,这应该处于u.i活动状态),但我不确定。
use*_*522 11
U u;
Run Code Online (Sandbox Code Playgroud)
不开始i子对象的生命周期。开始除类型数组之外的变量的生命周期char,unsigned char或者也不是专门限定为隐式创建对象的std::byte操作之一。[基本.介绍.对象]/13
因此,此时该i成员肯定不处于活动状态,并且数组对象也不处于活动状态。
正如 @Sebastian 在问题评论中提到的,在常量表达式中不允许调用std::construct_aton ,因为[expr.const]/6.1特别要求提供的指针指向其生命周期在常量表达式求值期间开始的对象(或是从 ) 返回的存储。u.istd::allocator
因此,Clang 对我来说似乎是正确的。这里有一个针对此问题的开放 GCC 错误。
但我不确定这是否是预期的解释,因为如果成员使用非数组类型,Clang 确实接受该程序,根据我的推理,这同样是不允许的。
相关措辞是此评论的结果。
无论如何,隐式对象创建并不打算发生在常量表达式中,尽管目前看来是这样(问题),请参阅CWG 问题 2469。
如果没有如下所述的隐式对象创建,则在需要常量表达式的上下文中的使用应该是格式错误的,独立于限制std::construct_at和以下考虑因素。
如果在常量表达式上下文之外使用,该构造是否具有定义的行为,我并不完全确定。
但我认为std::construct_at被指定为相当于new 表达式意味着它将调用operator new,它被指定为在它返回的存储中隐式创建对象。[基本.介绍.对象]/13
我并不完全清楚是否operator new必须是一个分配operator new调用才能实现这一点。我认为“在返回的存储区域中”的措辞不需要它。
i属于类型int[1],它是隐式生命周期类型,如果需要,可以通过有资格隐式创建对象的操作隐式创建它们。[基本.类型.一般]/9
因此,我认为这construct_at将隐式创建数组对象u.i并开始其生命周期。我还认为[basic.intro.object]/2将保证该对象成为联合的子对象,因此u.i将会引用它。
然而,考虑到操作的存储只是单个的大小,并且假设这也是[basic.intro.object]/13int中所指的存储,则只能在其中隐式创建一个长度的数组。因此,如果的长度大于,则隐式创建的数组无法与成员完全重叠,因此不能成为联合的子对象。1i1
在这种情况下,隐式对象创建无法实现return u.i[0];定义的行为。
这里有一个关于这个问题的讨论,似乎表明已经形成指向u.i其生命周期之外的第一个元素的指针是 UB,在这种情况下,construct_at带有数组的版本将更直接地具有 UB,但至少编译器auto x = u.i;在auto x = &u.i[0];不断的表达,没有抱怨。正如该答案的评论中提到的,这似乎也是错误的。
总而言之,我认为std::construct_at一般不能用于激活 a 的数组成员union。
但是,假设您将std::construct_at呼叫替换为
u.i[0] = 1;
Run Code Online (Sandbox Code Playgroud)
然后,此赋值将开始数组对象的生命周期,如[class.union.general]/6中所述。从 C++20 开始,这对于常量表达式也没有被取消资格。因此,如果在需要常量表达式的上下文中使用,代码不会格式错误,也不会在该上下文之外具有未定义的行为。