构造函数调用return语句

时间:2017-12-19 06:14:42

标签: c++ language-lawyer c++17

考虑以下示例:

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);左倾的吗?

3 个答案:

答案 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)

来自[class.copy.elision]

  

在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:如果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没有。