您为什么要明确移动转发参考?

Why would you explicitly move a forwarding reference?

我正在查看一些代码,我看到了以下函数:

template <typename... Args>
static return_t make_return(Args &&... args)
{
    // using std::forward<Args> will preserve lvalue args as such, but the point of this function
    // is to make a return, where the 99.9+% case is moving a local (lvalue) into the return pack.
    // Thus it forces a move, which will move `T&` args (but _not_ `const T&` args) into the
    // return pack so that users don't need to type out a bunch of std::moves themselves/
    // If they don't want implicit move they can just call std::make_tuple directly
    return std::make_tuple(std::move(args)...);
}

这里的文档让我很困惑。
您为什么要明确移动转发参考?
您不想在通用上下文中保留左值/右值吗?
我无法理解基本原理或此行为与推荐的 std::forward.

有何不同

换句话说,
我从未见过有人明确选择不完美转发转发参考。
有道理吗?

Why would you explicitly move a forwarding reference?

因为它没有被用于转发属性。是的,你是对的,我们通常 std::forward 一个转发参考。但在这种情况下,作者使用转发引用只是为了方便。如果它写成 make_return(Args&... args) 那么就不可能将右值传递给 make_return,因为非常量左值引用可能不会绑定到一个

通过使用转发引用,作者允许将任何值类别的值传递到函数中,如果需要 none 则不会产生额外的副本。文档在那里澄清函数签名不是用于转发,而只是用于绑定到它给出的任何参数以移出。

一个小例子揭示了作者的意图:

#include <tuple>
#include <iostream>

struct A {
  A() { }
  A(const A&) { std::cout << "copy\n"; }
  A(A&&) { std::cout << "move\n"; }
};

template <typename Arg>
static std::tuple<A> make_return(Arg&& arg) {
  return std::make_tuple(std::move(arg));
}

void f(const std::tuple<A>&) { }

void f1() {
  std::cout << "return local via make_tuple: ";
  A a{};
  f(std::make_tuple(a));
}

void f2() {
  std::cout << "return local via make_tuple(move): ";
  A a{};
  f(std::make_tuple(std::move(a)));
}

void f3() {
  std::cout << "return local via make_return: ";
  A a{};
  f(make_return(a));
}

void f4() {
  std::cout << "return const via make_tuple: ";
  const A a{};
  f(std::make_tuple(a));
}

void f5() {
  std::cout << "return const via make_tuple(move): ";
  const A a{};
  f(std::make_tuple(std::move(a)));
}

void f6() {
  std::cout << "return const via make_return: ";
  const A a{};
  f(make_return(a));
}

int main() {
  f1();
  f2();
  f3();
  f4();
  f5();
  f6();
}

输出:

return local via make_tuple: copy
return local via make_tuple(move): move
return local via make_return: move
return const via make_tuple: copy
return const via make_tuple(move): copy
return const via make_return: copy

在返回局部非常量变量的情况下,我们希望std::move其内容。这是可以使用 std::make_tuple(std::move(a)) 实现的,因为普通的 std::make_tuple(a) 会复制。为了节省一些打字时间,作者将 make_return 写成 std::make_tuple(std::move(a)) 的 shorthand:该示例表明 f3 的工作方式与 f2 相同。

当传递常量时,std::move 不会有任何区别,但也没有坏处。所以我们可以使用 std::make_tuple,但 make_return 也可以正常工作。案例 f4f5f6 都表现相同,表明在 make_return(在多个条目构成 return_t).

的情况

剩下的就是移动一个非函数本地的非常量变量,因此我们不想破坏它的内容。在这些情况下 make_return 是不需要的,需要重新手动调用 std::make_tuple(在适当的地方使用 std::move only)。

现在 std::forward 会是什么样子?将 make_return 的定义更改为利用

std::make_tuple(std::forward<Arg>(arg));

产生:

return local via tuple: copy
return local via tuple(move): move
return local via make_return: copy
return const via tuple: copy
return const via tuple(move): copy
return const via make_return: copy

因为 f3 中的 a 作为 const A& 传递。事实上,make_return 只是转发的逻辑,只是 std::move 的同义词,失去了我们希望获得的任何好处。

make_return() 是 return 使用 value 的元组,因为这个 value 不会不再需要,正如在 make_return(函数的结束范围)中使用的那样,没有必要使用 std::forward<>,因为它可以转发在副本中产生的左值引用(取决于实现),但是 处于范围末尾,因此不需要保留任何资源。

在 make_tuple 上强制 std::move,强制首先使用右值引用,省略可能的更多开销(取决于实现)。