考虑
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.
哪个编译器正确,为什么?
答案 0 :(得分:24)
我认为所有三个编译器都是错误的。
未定义为删除的显式默认函数仅在将其隐式声明为
constexpr
的情况下才可以声明为consteval
或constexpr
。如果某个函数在其第一个声明中被明确默认为默认值,则如果隐式声明为constexpr
,则该隐式被视为。{p>
何时将副本分配运算符隐式声明为constexpr
? [class.copy.assign]/10:
隐式定义的复制/移动赋值运算符是constexpr if
- X是文字类型,并且
- [...]
文字类型来自[basic.types]/10:
如果是,则类型是文字类型:
- [...]
可能具有cv资格的类类型,具有以下所有属性:
- 它有一个琐碎的析构函数,
- [...]
A1
没有琐碎的析构函数,因此它的隐式副本赋值运算符不是constexpr
。因此,该副本分配运算符的格式不正确(gcc和msvc bug可以接受)。
另外两个很好,拒绝A2
是一个叮叮当当。
请注意我引用的[dcl.fct.def.default]的最后一位。如果您明确指定了默认值,则实际上不必添加constexpr
。可能的话,将隐式constexpr
。
答案 1 :(得分:8)
C ++ 17标准规定:
15.8.2复制/移动分配运算符[class.copy.assign]
...10默认使用但未定义为删除的类X的复制/移动赋值运算符在它被奇数使用(6.2)时(例如,由重载决议选择它以进行赋值时)被隐式定义到其类类型的对象),或者在其首次声明后明确将其默认设置为默认值。 隐式定义的复制/移动分配运算符为
constexpr
,如果
(10.1)—X
是文字类型,并且
(10.2)—选择用于复制/移动每个直接基类子对象的赋值运算符是constexpr
函数,并且
(10.3)—对于X
的类类型(或其数组)的每个非静态数据成员,选择用来复制/移动该成员的赋值运算符是constexpr
函数。
在两种情况下,副本分配运算符都满足上述要求。在第一种情况下,由于非平凡的析构函数,我们具有非文字类型。
所以我认为Clang在第二种情况下拒绝代码是错误的。
有一个用Clang归档的名为Defaulted destructor prevents using constexpr on defaulted copy/move-operator的错误,它表现出与OP中的代码相同的症状。
错误报告状态中的注释:
当默认的析构函数被注释掉(即未由用户声明)时,错误就不再存在。
和
如果在复制赋值运算符之前声明析构函数,问题也会消失。
问题中的代码也是如此。
@YSC指出,此处的另一个相关引用是:[dcl.fct.def.default]/3,其中指出:
未定义为删除的显式默认函数仅在将其隐式声明为
constexpr
的情况下才可以声明为consteval
或constexpr
。如果某个函数在其第一个声明中被明确默认为默认值,则如果隐式声明为constexpr
,则该隐式被视为。{p>