MSVC不能return一个可以复制但不能移动的对象

MSVC cannot return an object that can be copied but cannot be moved

在摆弄复制省略时,我遇到了这种奇怪的行为:

class Obj {
 public:
  Obj() = default;

  Obj(Obj&&) = delete;
  Obj(const Obj&) { std::cout << "Copy" << std::endl; }
};

Obj f1() {
  Obj o;
  return o; // error C2280: move constructor is deleted
}

Obj f2() {
  Obj o;
  return Obj(o); // this however works fine
}

int main() {
  Obj p = f1();
  Obj q = f2();

  return 0;
}

GCC 和 Clang 接受此代码并且能够在这两种情况下使用复制省略。

f1() 中 MSVC 抱怨它不能 return o 因为 Obj 的移动构造函数被删除了。但是,我希望它能够依靠复制构造函数。 这是 MSVC 中的错误还是这种期望的行为(我不明白)和 GCC / Clang 过于宽松?

如果我提供移动构造函数,MSVC 可以在编译为 Release.

时省略移动

有趣的是 MSVC 能够编译 f2()。据我了解,这是由于当构造函数调用的结果为 returned 时强制复制省略。然而,如果我手动复制它,我只能按值 return o 感觉违反直觉。

我知道这种情况可能与实际应用无关,因为可复制对象通常也是可移动的,但我对底层机制很感兴趣。

这里是测试用的在线例子:https://godbolt.org/z/sznds7

f1() 上缺少错误是 clang 和 gcc 中的错误。 It is fixed in clang's tip-of-trunk.

f1() 不符合强制复制省略的条件。

删除的函数参与重载决议。如果它们被选为最佳重载,则程序是病式的。在f1()中,删除的移动构造函数由重载决议选择。

f2()as of C++17, copy/move elision is guaranteed 中,因此未完成 move/copy 构造函数的重载解析。在 C++11/14 中,f2() 也是一个错误(与 f1() 相同的错误)因为 copy/move 省略是 not 保证。

另请参阅此指南:,诚然,它是在 C++17 之前编写的。

哦,惭愧,我才知道另一个答案是Howard Hinnant,他的文章让我明白了我在这里痛苦地试图解释的东西,有点荒谬。 ..

因为复制和移动构造函数都声明了,所以它们都存在。 特别是在这里,您小心地定义了自己的复制构造函数;否则它将被定义为删除(参见 this presentation 的第 28 页)。

删除 方面只是关于定义的细节,但它们实际上都已声明,然后有资格重载决议。 在 f1() 中,如果发生复制省略,则无需在复制和移动构造函数之间进行选择; none 个已被使用。 另一方面,如果没有发生复制省略,则必须选择最佳重载来构造结果;这是移动构造函数,因为它存在(已声明,参见here),最后发现定义已删除,但为时已晚,已经做出选择。

f2()中,显式请求显式拷贝,那么拷贝构造函数是最好的选择。

令人困惑的是,当我们阅读 =delete 时,我们认为 « this cannot be chosen in overload resolution » 但这是错误的; =delete 仅在 重载解析之后,当找不到更好的匹配时为时已晚。