考虑
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.
(Qazxswpoi)
哪个编译器是正确的,为什么?
我认为所有三个编译器都是错误的。
未定义为已删除的显式默认函数只有在被隐式声明为[dcl.fct.def.default]/3时才可以声明为
constexpr
或consteval
。如果函数在其第一个声明中显式默认,则隐式声明将隐式地认为是constexpr
。
何时复制赋值运算符隐式声明constexpr
? constexpr
:
隐式定义的复制/移动赋值运算符是constexpr if
- X是文字类型,和
- [...]
文字类型的位置,来自[class.copy.assign]/10:
类型是文字类型,如果它是:
- [...]
- 一个可能具有cv限定的类类型,它具有以下所有属性: 它有一个简单的析构函数, [...]
[basic.types]/10没有一个简单的析构函数,因此它的隐式复制赋值运算符不是A1
。因此,复制赋值运算符格式不正确(gcc和msvc错误接受)。
另外两个很好,这是一个拒绝constexpr
的铿锵声。
注意我引用的[dcl.fct.def.default]的最后一位。如果您明确默认,则实际上不必添加A2
。这可能是隐含的constexpr
。
C ++ 17标准规定:
15.8.2复制/移动赋值运算符
constexpr
...[class.copy.assign]默认使用默认情况下默认定义为已删除的类X的复制/移动赋值运算符(6.2)(例如,当通过重载决策选择它以分配给其类类型的对象时) )或在第一次声明后明确违约时。隐式定义的复制/移动赋值运算符是10 if (10.1) -
constexpr
是一个字面类型,和 (10.2) - 选择复制/移动每个直接基类子对象的赋值运算符是X
函数,并且 (10.3) - 对于类型(或其数组)的constexpr
的每个非静态数据成员,选择复制/移动该成员的赋值运算符是X
函数。
复制赋值运算符在两种情况下满足上述要求。在第一种情况下,由于非平凡的析构函数,我们有一个非文字类型。
所以我认为Clang在第二种情况下拒绝代码是错误的。
有一个提交给Clang的错误标题:constexpr
,它显示与OP中的代码相同的症状。
错误报告的评论说明:
当默认的析构函数被注释掉(即没有用户声明)时,错误就不复存在了。
和
如果在复制赋值运算符之前声明析构函数,问题也会消失。
对于问题中的代码也是如此。
正如@YSC指出的那样,另一个相关的引用是:Defaulted destructor prevents using constexpr on defaulted copy/move-operator,其中指出:
未定义为已删除的显式默认函数只有在被隐式声明为[dcl.fct.def.default]/3时才可以声明为
constexpr
或consteval
。如果函数在其第一个声明中显式默认,则隐式声明将隐式地认为是constexpr
。