std::forward 如何接收到正确的参数?

How does std::forward receive the correct argument?

考虑:

void g(int&);
void g(int&&);

template<class T>
void f(T&& x)
{
    g(std::forward<T>(x));
}

int main()
{
    f(10);
}

既然 id-expression x 是一个左值,而 std::forward 有左值和右值的重载,为什么调用不绑定到 std::forward 的重载一个左值?

template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;

why doesn't the call bind to the overload of std::forward that takes an lvalue?

确实如此,但是 std::forward does not deduce its template argument, you tell it what type it is, and that's where the magic occurs. You're passing a prvalue to f() so f() deduces [T = int]. It then calls the lvalue overload of forward, and due to reference collapsing return 类型和 forward 中发生的 static_cast<T&&> 类型都是 int&&,从而调用 void g(int&&) 重载。

如果要将左值传递给 f()

int x = 0;
f(x);

f() 推导 [T = int&],再次调用 forward 的相同左值重载,但这次 return 类型和 static_cast<T&&> 都是 int&,同样是因为引用折叠规则。然后这将调用 void g(int&) 重载。

Live demo


Howard 已经很好地回答了为什么需要 forward 的右值重载的问题,但我将添加一个人为的示例来显示两个版本的实际效果。

这两个重载背后的基本思想是,在传递给 forward 的表达式的结果产生右值(纯右值或 x 值)的情况下,将调用右值重载。

假设您有一个类型 foo,它有一对引用限定的 get() 成员函数重载。带有 && 限定符的一个 return 是 int 而另一个 return 是 int&.

struct foo
{
    int i = 42;
    int  get() && { return i; }
    int& get() &  { return i; }
};

然后说 f() 对传递给它的任何内容调用 get() 成员函数,并将其转发给 g()

template<class T>
auto f(T&& t)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
    g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get()));
}

foo foo1;
f(foo1);   // calls lvalue overload of forward for both calls to forward

f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t)
                    // but calls rvalue overload for outer call to forward

Live demo

确实绑定到std::forward的重载采用左值:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;

它与 T == int 绑定。此函数指定为 return:

static_cast<T&&>(t)

因为f中的T推导为int。因此,此重载将左值 int 转换为 xvalue,其中:

static_cast<int&&>(t)

因此调用 g(int&&) 重载。

总而言之,std::forward 的左值重载可以将其参数转换为左值或右值,具体取决于调用它的 T 的类型。

std::forward 的右值重载只能转换为右值。如果您尝试调用该重载并强制转换为左值,则程序格式错误(需要编译时错误)。

因此重载 1:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;

捕获左值。

过载 2:

template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;

捕获右值(即 xvalues 和 prvalues)。

Overload 1 可以将其左值参数转换为 lvalue 或 xvalue(后者将被解释为用于重载解析目的的右值)。

Overload 2 只能将其右值参数转换为 xvalue(出于重载决策目的,它将被解释为右值)。

过载 2 适用于 N2951 中标记为 "B. Should forward an rvalue as an rvalue" 的案例。简而言之,这种情况可以实现:

std::forward<T>(u.get());

您不确定 u.get() return 是左值还是右值,但无论哪种方式,如果 T 不是左值引用类型,您想要移动 returned值。但是你不使用 std::move 因为如果 T 左值引用类型,你 不想 从 return.

移动

我知道这听起来有点做作。但是 N2951 遇到了很大的麻烦来设置 motivating 用例 std::forward 应该如何与显式提供的模板参数和隐式提供的表达式的所有组合一起工作普通参数的类别。

读起来并不容易,但是 std::forward 的每个模板和普通参数组合的基本原理都在 N2951 中。当时这在委员会中引起争议,而且不是一件容易的事。

std::forward 的最终形式与 N2951 提出的不完全相同。然而,它 确实 通过了 N2951.

中提出的所有六项测试

std::forward如何接收到正确的参数?

完美转发,如您代码:

template<class T>
void f(T&& x)
{
    g(std::forward<T>(x));
}

请注意:std::forward 需要函数参数和模板类型参数。 模板参数 T 将对传递给 param 的参数是左值还是右值进行编码,然后转发使用它。 在这种情况下,不管 x 引用什么,x 本身是一个左值,选择重载 1 ,并且 x 是转发(通用)引用 parameters.Rules 如下:

  1. 如果f参数的expr是左值,xT都是左值引用 类型
  2. 如果f参数的expr是右值,T将是非引用类型

例如使用gcc源代码:

  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); } //overload 1


  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
            " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }// overload 2
  • 如果传函数f类型的左值为stringstring&,对于 正向函数 _Tp 将是 string&__t 将是 string & & 等于 string&,_Tp&&string& && 等于 string&.(参考塌陷)
  • 如果传递函数f类型的右值为stringstring&&,对于 正向函数 _Tp 将是 string__t 将是 string&,_Tp&& 将是 string&&.

有什么作用?

  • 如果右值变为右值(重载 2)或左值变为左值(重载 1),无事可做,只需 return.
  • 如果你不小心或其他原因,将右值引导到左值,static_assert会给你一个编译错误。(Overload 2)
  • 如果做左值转右值(Overload 1),和std::move一样,但不方便。

正如 Scott Meyers 所说:

Given that both std::move and std::forward boil down to casts, the only difference being that std::move always casts, while std::forward only sometimes does, you might ask whether we can dispense with std::move and just use std::forward everywhere. From a purely technical perspective, the answer is yes: std::forward can do it all. std::move isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be, well, yucky.

什么是右值重载?

我不确定,它应该用在你真正需要的地方 it.Such 正如 Howard Hinnant 所说:

Overload 2 is for the case labeled "B. Should forward an rvalue as an rvalue" in N2951. In a nutshell this case enables:

std::forward<T>(u.get());

where you are unsure if u.get() returns an lvalue or rvalue, but either way if T is not an lvalue reference type, you want to move the returned value. But you don't use std::move because if T is an lvalue reference type, you don't want to move from the return.

总之就是让我们用T的类型来决定是否移动