考虑以下示例:
foo()
Clang和GCC不同意该计划是否有效。 GCC尝试在调用-fno-elide-constructors
期间初始化临时值时调用move构造函数,该函数已被删除,从而导致编译错误。即使使用result
,Clang也可以处理这个问题。
有人可以解释为什么在这种情况下允许GCC调用移动构造函数吗?不是const obj = [{
'booktitle': 'Leading',
'bookid': '56353',
'bookauthor': 'Sir Alex Ferguson'
}, {
'booktitle': 'How Google Works',
'bookid': '73638',
'bookauthor': 'Eric Smith'
}, {
'booktitle': 'The Merchant of Venice',
'bookid': '37364',
'bookauthor': 'William Shakespeare'
}];
const res = obj.reduce((a, b) => {
for(let i in b) {
if(!a[i]) {
a[i] = [];
}
a[i].push(b[i]);
}
return a;
}, {});
console.log(res);
左倾的吗?
答案 0 :(得分:11)
我要引用C ++ 17(n4659),因为那里的措辞最为明确,但之前的修订说的相同,对我来说不太清楚。这是[class.copy.elision]/3,强调我的:
在以下复制初始化上下文中,可能会执行移动操作 用来代替复制操作:
如果return语句中的表达式是一个(可能带括号的)id-expression,它使用自动命名对象 在body或parameter-declaration-clause中声明的存储持续时间 最里面的封闭函数或lambda表达式,或
[...]
重载决策首先选择复制的构造函数 表现为好像该对象是由右值指定的。 如果 第一个重载决策失败或未执行,或者类型 所选构造函数的第一个参数不是右值 引用对象的类型(可能是cv-qualified),重载 再次执行分辨率,将对象视为左值。 [注意:必须执行此两阶段重载决策 无论是否会发生复制。它决定了 如果未执行elision则调用的构造函数,以及所选的 即使呼叫被省略,也必须可以访问构造函数。 - 结束 音符]
所以这就是为什么实际上Clang和GCC都会尝试首先调用此举。行为上的差异是因为Clang以不同的方式遵循粗体文本。超载解决方案发生了,发现了一个移动,但称它是不正确的。所以Clang再次执行它并找到副本c'tor。
GCC只是停在其轨道上,因为在重载决议中选择了删除的功能。
我确实相信Clang在这里是正确的,如果还有别的话。尝试移动返回的值并将其作为回退进行复制是此优化的预期行为。我觉得海湾合作委员会不应该停止,因为它找到了一个删除的移动c'tor。如果它是一个被定义删除的默认移动,那么编译器都不会。该标准意识到 案例([over.match.funcs]/8)中的潜在问题:
默认移动特殊功能([class.copy]),定义为 删除的所有候选函数集都被排除在外 上下文。
答案 1 :(得分:8)
在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:如果return语句中的表达式是(可能带括号的) id-expression ,命名具有在正文中声明的自动存储持续时间的对象 [...]
首先执行重载决策以选择复制的构造函数,就好像该对象是由rvalue指定的一样。 如果第一个重载决策失败或未执行,或如果所选构造函数的第一个参数的类型不是对象类型的右值引用 (可能是cv-qualified),再次执行重载决策,将对象视为左值。
在return result;
中,我们正好在该段中提到的情况中 - result
是一个 id-expression ,命名一个在正文中声明自动存储持续时间的对象。因此,我们首先执行重载决策,就像它是一个右值。
重载决议会找到两个候选人:X(X const&)
和X(X&&)
。后者is preferred。
现在,重载决策失败意味着什么?来自[over.match]/3:
如果存在最佳可行功能并且是唯一的,则重载决策成功并将其作为结果产生。否则重载解析失败,调用格式不正确。
X(X&&)
是一个独特的,最可行的函数,因此重载决策成功。这个复制上下文对我们来说有一个额外的标准,但是我们也满足它,因为这个候选者的第一个参数的类型是({cv-qualified)rvalue对X
的引用。两个盒子都经过检查,所以我们就此止步。作为左值,我们不再继续执行重载决策,我们已经选择了我们的候选者:移动构造函数。
一旦我们选择了它,程序就会失败,因为我们正在尝试调用已删除的功能。但只有在那一点,而不是更早。考生不会被排除在过载集之外被删除,否则删除重载几乎不会有用。
这是clang bug 31025。
答案 2 :(得分:0)
result
返回后, foo()
不是左值,而是prvalue,因此可以调用移动构造函数。
来自CppReference:
以下表达式是 prvalue表达式:
- 函数调用或重载的运算符表达式,其返回类型为非引用,例如
str.substr(1, 2)
,str1 + str2
或{ {1}}
Clang可能检测到返回值未被使用并直接丢弃,而GCC没有。