使用移动 CTOR 的 constexpr 对象的 constexpr 数组
constexpr array of constexpr objects using move ctor
我有一个带有constexpr
值构造函数的类,但没有复制或移动 ctor
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
};
int main() {
constexpr C arr[] = {1, 2};
}
我发现这段代码不起作用,因为它实际上试图使用 move 构造函数进行C
而不是值构造函数来就地构造。 一个问题是我希望这个对象是不可移动的(出于测试目的(,但我想"好吧,好吧,我会添加一个移动构造函数。
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
C& operator=(C&&) = delete;
C(C&&) { /*something*/ } // added, assume this must be non trivial
};
好吧,现在它使用移动构造函数,并且在 gcc 下一切正常,但是当我使用 clang 时,它会抱怨,因为移动构造函数没有标记为constexpr
error: constexpr variable 'arr' must be initialized by a constant expression
constexpr C arr[] = {1, 2};
如果我标记移动构造函数constexpr
它在 gcc 和 clang 下工作,但问题是我希望在移动构造函数中拥有代码(如果它运行的话(,并且 constexpr 构造函数必须有空体。 (我在移动 ctor 中有代码的原因不值得深入探讨(。
那么谁就在这里?我的倾向是,拒绝代码是正确的。
注意
它确实使用初始值设定项列表和不可复制的不可移动对象进行编译,如下所示:
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
C& operator=(C&&) = delete;
C(C&&) = delete;
};
int main() {
constexpr C arr[] = {{1}, {2}};
}
我主要关心的是上面的哪个编译器是正确的。
那么谁就在这里?
Clang拒绝代码是正确的。[expr.const]/2:
条件表达式
e
是核心常量表达式,除非e
的评估,遵循抽象机的规则 (1.9(,将计算以下表达式之一:
- 对文本类、
constexpr
函数或隐式调用的constexpr
构造函数以外的函数的调用 一个微不足道的析构函数 (12.4(
显然,您的移动构造函数不是constexpr
构造函数 - [dcl.constexpr]/2
同样,构造函数声明中使用的
constexpr
说明符 将该构造函数声明为constexpr
构造函数。
对constexpr
对象的初始值设定项的要求在 [dcl.constexpr]/9 中:
[...] 出现在其初始值设定项中的每个完整表达式都应是 常量表达式。[ 注意:每个隐式转换都包含在 转换初始值设定项表达式和所使用的每个构造函数调用 因为初始化是这种完整表达式的一部分。— 尾注 ]
最后,移动构造函数通过使用相应的初始值设定项子句 - [dcl.init] 对数组元素进行复制初始化来调用:
否则(即,对于其余的副本初始化情况(, 可从源转换的用户定义的转换序列 键入为目标类型或(使用转换函数时( 如13.3.1.4中所述枚举其派生类, 最好的一个是通过过载分辨率(13.3(选择的。如果 转换无法完成或模棱两可,初始化为 格式不正确。所选函数使用初始值设定项调用 表达作为其参数;如果函数是构造函数,则 调用初始化 CV 非限定版本的临时版本 目标类型。临时是原则。调用的结果 (这是构造函数情况的临时(然后用于 根据上述规则,直接初始化对象 复制初始化的目标。
在第二个示例中,复制列表初始化适用 - 并且不引入临时。
顺便说一句:GCC 4.9 不会编译上述内容,即使没有提供任何警告标志。
§8.5 [dcl.init]/p17:
初始值设定项的语义如下所示。目标类型为 正在初始化的对象或引用的类型以及源 type 是初始值设定项表达式的类型。如果初始值设定项是 不是单个(可能是括号(表达式,源类型是 未定义。
- 如果初始值设定项是(非括号(大括号初始化列表,则对象或引用是列表初始化的 (8.5.4(。
- [...]
- 如果目标类型是(可能符合 cv 条件的(类类型:
- 如果初始化是直接初始化,或者如果是复制初始化,其中源的 cv 非限定版本 类型与 目的地, [...]
- 否则(即,对于剩余的复制初始化情况(,可以从源转换的用户定义的转换序列 键入为目标类型或(使用转换函数时( 如13.3.1.4中所述枚举其派生类, 最好的一个是通过过载分辨率(13.3(选择的。如果 转换无法完成或模棱两可,初始化为 格式不正确。所选函数使用初始值设定项调用 表达作为其参数;如果函数是构造函数,则调用 初始化 CV 非限定版本的临时版本 目标类型。临时是原则。调用的结果 (这是构造函数情况的临时(然后用于 根据上述规则,直接初始化对象 复制初始化的目标。在某些情况下,一个 允许实施以消除此固有的复制 通过直接构造中间结果进行直接初始化 到正在初始化的对象中;参见 12.2、12.8。
- [...]
§8.5.1 [dcl.init.aggr]/p2:
当聚合由初始值设定项列表初始化时,如指定 在 8.5.4 中,初始值设定项列表的元素取为 聚合成员的初始值设定项,下标递增 或会员订单。每个成员都从 相应的初始值设定项子句。如果初始值设定项子句是 表达式和缩小转换 (8.5.4( 是转换所必需的 表达式,程序格式不正确。[ 注意:如果 初始值设定项子句本身就是一个初始值设定项列表,成员是 列表初始化,这将导致递归应用 如果成员是聚合,则在此部分中的规则。—尾注 ]
§8.5.4 [dcl.init.list]/p3:
对象或 T 类型的引用的列表初始化定义为 遵循:
- 如果 T 是聚合,则执行聚合初始化 (8.5.1(。
- [...]
- 否则,如果 T 是类类型,则考虑构造函数。枚举适用的构造函数并选择最佳构造函数 通过重载解析(13.3、13.3.1.7(。如果变窄 转换(见下文(是转换任何参数所必需的, 程序格式不正确。
- [...]
对于constexpr C arr[] = {1, 2};
,聚合初始化复制初始化每个元素从相应的初始值设定项子句,即1
和2
。如 §8.5 [dcl.init]/p17 中所述,这将构造一个临时C
,然后从临时元素直接初始化数组元素,这需要一个可访问的复制或移动构造函数。(可以省略复制/移动,但构造函数必须仍然可用。
对于constexpr C arr[] = {{1}, {2}};
,元素是复制列表初始化的,它不构造临时的(注意在§8.5.4 [dcl.init.list]/p3中没有提到临时构造(。
- 任意大小的 constexpr 数组是否可以用作 switch 语句中的案例?
- C++访问静态 constexpr 数组
- 从非类型模板参数声明 constexpr 数组的可移植方法
- 正在初始化函数指针的constexpr数组
- constexpr数组初始化
- 类本身内部的类对象的静态constexpr数组
- 使用模板函数初始化 constexpr 数组
- 用另一个 constexpr 数组对成员数组进行大括号初始化
- 如何将constexpr数组传递到函数中
- 在 C++11 中将静态 constexpr 数组转换为模板参数
- MSVC 错误,将 constexpr 数组作为模板非类型参数
- 将结构转换为 constexpr 数组uint8_t
- 如何以静态方式使用另一个 constexpr 数组初始化一个数组
- C++constexpr数组查找:内存开销?其他问题
- 将 constexpr 数组扩展为一组非类型模板参数
- 如何使用 std::copy 将一个 constexpr 数组复制到另一个 constexpr 数组
- 如何定义模板类专业化的静态constexpr数组成员
- 将ConstexPR数组复制到类中
- 正在初始化带模式的“constexpr”数组
- 将一个 constexpr 数组初始化为其他两个 constexpr 数组的总和