Ale*_*wer 13 c++ language-lawyer c++17
请考虑以下示例:
class X {
public:
X() = default;
X(const X&) = default;
X(X&&) = delete;
};
X foo() {
X result;
return result;
}
int main() {
foo();
}
Run Code Online (Sandbox Code Playgroud)
Clang和GCC对该计划是否有效表示不同意见.GCC尝试在调用期间初始化临时函数时调用move构造函数,该函数foo()已被删除,从而导致编译错误.Clang处理这个很好,即使有-fno-elide-constructors.
任何人都可以解释为什么GCC被允许在这种情况下调用移动构造函数?不是result左值?
Sto*_*ica 11
我要引用C++ 17(n4659),因为那里的措辞最为明确,但之前的修改说的相同,对我来说不太清楚.这是[class.copy.elision]/3,强调我的:
在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:
如果return语句中的表达式是一个(可能带括号的)id-expression,它指定一个对象,该对象具有在最内层封闭函数或lambda表达式的body或parameter-declaration-clause中声明的自动存储持续时间,或者
[...]
首先执行重载决策以选择副本的构造函数,就好像该对象是由rvalue指定的一样.如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值. [注意:无论是否发生复制省略,都必须执行此两阶段重载决策.如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数. - 结束说明]
所以这就是为什么实际上Clang和GCC都会尝试先调用此举.行为上的差异是因为Clang以不同的方式遵循粗体文本.超载解决方案发生了,发现了一个移动,但称它是不正确的.所以Clang再次执行它并找到副本c'tor.
GCC只是停在其轨道上,因为在重载决议中选择了删除的功能.
我相信Clang在这里是正确的,如果有的话,在精神上.尝试移动返回的值并作为回退进行复制是此优化的预期行为.我觉得海湾合作委员会不应该停止,因为它找到了一个删除的移动c'tor.如果它是一个被定义删除的默认移动,那么编译器都不会.该标准意识到该情况下的潜在问题([over.match.funcs]/8):
定义为已删除的默认移动特殊函数([class.copy])将从所有上下文中的候选函数集中排除.
在以下复制初始化上下文中,可以使用移动操作而不是复制操作:如果return语句中的表达式是(可能带括号的)id-expression,它指定在body [...中声明的自动存储持续时间的对象. ..]
首先执行重载决策以选择副本的构造函数,就好像该对象是由rvalue指定的一样.如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值.
在return result;我们正好在该段落中提到的情况下 - result是一个id-expression命名一个具有在体内声明的自动存储持续时间的对象.因此,我们首先执行重载决策,就好像它是一个右值.
重载决议将找到两个候选人:X(X const&)和X(X&&).后者是优选的.
现在,重载决策失败意味着什么?来自[over.match]/3:
如果存在一个最好的可行函数并且是唯一的,则重载解析成功并将其作为结果产生.否则重载解析失败并且调用格式错误.
X(X&&)是一个独特的,最好的可行功能,因此重载分辨率成功.这个复制上下文对我们来说有一个额外的标准,但我们也满足它,因为这个候选者的第一个参数的类型是(可能是cv-qualified)rvalue引用X.两个盒子都经过检查,所以我们就此止步.作为左值,我们不再继续执行重载决策,我们已经选择了我们的候选者:移动构造函数.
一旦我们选择它,程序就会失败,因为我们试图调用已删除的函数.但只是在那一点上,而不是更早.候选者不会被排除在过载集之外被删除,否则删除重载将不会有用.
这是铿锵声31025.
| 归档时间: |
|
| 查看次数: |
514 次 |
| 最近记录: |