将默认赋值运算符声明为 constexpr:哪个编译器是正确的?
Declaring defaulted assignment operator as constexpr: which compiler is right?
>考虑
struct A1 {
constexpr A1& operator=(const A1&) = default;
~A1() {}
};
struct A2 {
constexpr A2& operator=(const A2&) = default;
~A2() = default;
};
struct A3 {
~A3() = default;
constexpr A3& operator=(const A3&) = default;
};
GCC 和 MSVC 接受所有三种结构。Clang 拒绝A1
和A2
(但接受A3
),并显示以下错误消息:
<source>:2:5: error: defaulted definition of copy assignment operator is not constexpr constexpr A1& operator=(const A1&) = default; ^ <source>:6:5: error: defaulted definition of copy assignment operator is not constexpr constexpr A2& operator=(const A2&) = default; ^ 2 errors generated.
(现场演示)
哪个编译器是正确的,为什么?
我认为这三个编译器都是错误的。
[dcl.fct.def.default]/3 说:
未定义为已删除的显式默认函数只有在隐式声明为
constexpr
时才可以声明为constexpr
或consteval
。如果函数在其第一个声明上显式默认,则隐式认为它是constexpr
隐式声明是否默认。
复制赋值运算符何时隐式声明为constexpr
?[class.copy.assign]/10:
隐式定义的复制/移动赋值运算符是 constexpr,如果
- X 是文本类型,并且
- [...]
如果文本类型为,则来自 [basic.types]/10:
如果类型是以下类型,则类型为文本类型:
- [...]
可能符合 CV 条件的类类型,具有以下所有属性:
- 它有一个微不足道的析构函数,
- [...]
A1
没有简单的析构函数,因此其隐式复制赋值运算符不是constexpr
。因此,复制赋值运算符格式不正确(gcc 和 msvc 错误接受)。
另外两个都很好,拒绝A2
是个叮当声。
请注意我引用的[dcl.fct.def.default]的最后一点。如果您显式默认,则实际上不必添加constexpr
。在可能的情况下,这将是隐含constexpr
的。
C++17 标准规定:
15.8.2 复制/移动赋值运算符 [class.copy.assign]
...10 当 odr 使用 (6.2) 时(例如,当重载解析选择它分配给其类类型的对象时),或者在第一次声明后显式默认时,隐式定义默认且未定义为已删除的类 X 的复制/移动赋值运算符。隐式定义的复制/移动赋值运算符
constexpr
if
(10.1) —X
是文字类型,和
(10.2) — 选择复制/移动每个直接基类子对象的赋值运算符是一个constexpr
函数,并且
(10.3) — 对于类类型(或其数组)的每个非静态数据成员X
,选择复制/移动该成员的赋值运算符是一个constexpr
函数。
复制赋值运算符在其中两种情况下满足上述要求。在第一种情况下,由于非平凡析构函数,我们有一个非文字类型。
所以我认为Clang在第二种情况下拒绝代码是错误的。
有一个提交给 Clang 的错误,标题为:默认析构函数阻止在默认的复制/移动运算符上使用 constexpr,该运算符显示与 OP 中的代码相同的症状。
错误报告中的注释指出:
当默认析构函数被注释掉(即不是用户声明的)时,错误将不复存在。
和
如果在复制赋值运算符之前声明析构函数,问题也会消失。
问题中的代码也是如此。
正如@YSC指出的,这里另一个相关的引用是:[dcl.fct.def.default]/3,其中指出:
未定义为已删除的显式默认函数只有在隐式声明为
constexpr
时才可以声明为constexpr
或consteval
。如果函数在其第一个声明上显式默认,则隐式认为它是constexpr
隐式声明是否默认。
- 重载Singly Linked List中的赋值运算符
- 使用赋值运算符重载从类中返回jobject
- 标准库类型的赋值运算符的引用限定符
- 复制构造函数、赋值运算符C++
- 标准::变体的赋值运算符
- 移动赋值运算符;尝试引用已删除的函数.我该如何解决这个问题?
- 基类和派生类的多态赋值运算符
- 为用户定义的类正确调用复制构造函数/赋值运算符
- CRTP 中的复制赋值运算符 - gcc vs clang 和 msvc
- 为什么初始化时没有调用重载赋值运算符?
- 赋值运算符重载和自赋值
- C++矢量复制构造函数和赋值运算符是否也复制保留空间?
- 如果类在 C++ 中具有常量或引用类型的非静态数据成员,为什么编译器不提供默认赋值运算符?
- 为什么在取消引用的指向接口的指针上使用赋值运算符不是编译器错误
- 使用赋值运算符复制两个类中的数组时出现编译器错误
- 将默认赋值运算符声明为 constexpr:哪个编译器是正确的?
- 为什么C++编译器会创建复制构造函数和复制赋值运算符
- 如果类具有引用数据成员,为什么编译器不合成默认赋值运算符
- 从派生的 base 赋值运算符中的编译器辅助
- 编译器的区别:扩展x3::变体需要用gcc定义复制构造函数、复制赋值运算符和默认构造函数,但不需要clang