为什么返回兼容类型时需要显式 std::move?

Why explicit std::move is needed when returning compatible type?

我正在看 STL 的“Don’t Help the Compiler”演讲,他在幻灯片 26 上有一个类似的例子:

struct A
{
  A() = default;
  A(const A&) { std::cout << "copied" << std::endl; }
  A(A&&) { std::cout << "moved" << std::endl; }
};

std::pair<A, A> get_pair()
{
  std::pair<A, A> p;
  return p;
}

std::tuple<A, A> get_tuple()
{
  std::pair<A, A> p;
  return p;
}

std::tuple<A, A> get_tuple_moved()
{
  std::pair<A, A> p;
  return std::move(p);
}

有了这个,下面的调用:

get_pair();
get_tuple();
get_tuple_moved();

产生这个输出:

moved
moved
copied
copied
moved
moved

See MCVE in action.

get_pair的结果是移动构造的,这是预期的。 NRVO 也可能完全省略了一个动作,但这不在当前问题的主题范围内。

get_tuple_moved 的结果也是移动构造的,这是明确指定的。但是,get_tuple 的结果是复制构造的,这对我来说是完全不明显的。

我认为传递给 return 语句的任何表达式都可能被认为具有隐含的 move,因为编译器知道它无论如何都会超出范围。好像我错了。有人可以澄清一下,这是怎么回事吗?

另见相关但不同的问题:When should std::move be used on a function return value?

get_tuple() 中的 return 语句应该使用移动构造函数进行复制初始化,但是由于 return 表达式的类型和 return类型不匹配,则选择复制构造函数。在 C++14 中进行了更改,现在有一个重载决议的初始阶段,当 return 语句只是一个在主体中声明的自动变量时,它会将其视为右值。

相关写法见[class.copy]/p32:

When the criteria for elision of a copy/move operation are met, [..], or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body [..], overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

所以在 C++14 中,所有输出都应该来自 A 的移动构造函数。

clang 和 gcc 的主干版本已经实现了这一变化。要在 C++11 模式下获得相同的行为,您需要在 return 语句中使用显式 std::move()。