如果调用临时对象的显式对象成员函数,是否必须在显式对象参数中省略临时对象的移动?
考虑以下示例,其中struct A
移动构造函数已被删除并且f(this A)
为临时对象调用A
:
struct A {
A() {}
A(A&&) = delete;
void f(this A) {}
};
int main() {
A{}.f();
}
该程序在 GCC 中被接受,但是 Clang 和 MSVC 都拒绝它:
调用已删除的“A”构造函数
错误 C2280:'A::A(A &&)':尝试引用已删除的函数
在线演示:https://gcc.godbolt.org/z/rbv14cnz5
这里哪个编译器是正确的?
这是CWG2813。正如问题描述所指出的,C++23 中的措辞要求点运算符的左操作数有一个 glvalue,这意味着如果左操作数是 prvalue,则必须先将其转换为 glvalue(具体化),以防止复制/移动省略。这种结果是不可取的;我们希望 prvalue 直接初始化显式对象参数。因此,对措辞进行了更改,并已批准为 DR。编译器需要一段时间才能赶上,但目的是应该接受该示例。
GCC 是正确的。不幸的是,标准没有明确说明这一事实。相反,正是由于在各个地方没有针对显式对象参数的特殊措辞,才使得您的程序正确。
表达式由嵌入在类成员访问表达式中
A{}.f()
的表达式组成,而类成员访问表达式本身又嵌入在函数调用表达式中。从成员访问开始,在本例中没有任何措辞要求接收方操作数是或转换为 glvalue。第 2 条仅对非静态数据成员施加此限制:A{}
A{}.f
A{}.f()
[expr.ref]
处理函数访问的子句 (7.3) 只是说
再次强调,重要的部分是缺少任何限制
A{}
为泛左值的措辞。继续进行过载解析,相关规则是
[over.call.func]/2
:因此,在您的情况下,为了进行过载解析,
A{}.f()
变为f(A{})
,而所讨论的过载集只是f(this A)
。然后过载解析应该会成功。Clang 和 MSVC 的错误消息表明它们已成功完成这一步。显式对象参数不受函数参数初始化规则的特殊处理。隐式对象参数是特殊情况。
[expr.call]/6
内容如下:再次,会导致您的代码失败的句子(最后一句)在这种情况下并不适用,因为
f
它不是隐式对象成员函数。最后,随着函数调用的求值,
this A
参数f
需要通过 prvalue 表达式 初始化A{}
。此操作成功,无需移动构造函数的参与,通过[dcl.init.general]/16.6
(毕竟,表达式
A{}
没有使用移动构造函数。)顺便说一句,我要指出的是,你对这个问题的措辞并不完全正确。
A{}
不是“临时的”;它是一个 prvalue。prvalue 是一个表达式,当求值时,它会用一个对象初始化给定的内存块。当一个函数void g(A);
被 调用g(A{})
时,没有移动这一事实不应被认为是“省略”。只有在比较 C++11 和 C++03 时,以“省略”的角度思考才有意义。从 C++11 开始,根本没有移动可以省略。你的问题最好写成“当一个带有按值显式对象参数的函数在类类型的 prvalue 上被调用时,临时值是否会被实现并从中移动?”我给出的答案是“不,prvalue 直接初始化参数,没有临时值,也没有移动。”