繁体   English   中英

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具有lvalues和rvalues的重载,为什么调用不会绑定到带有左值的std::forward的重载?

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

确实绑定std::forward一个左值的std::forward的重载:

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

它与T == int绑定。 指定此函数返回:

static_cast<T&&>(t)

因为fT推导为int 所以这个重载将lvalue int强制转换为xvalue:

static_cast<int&&>(t)

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

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

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

超载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;

捕获rvalues(xvalues和prvalues)。

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

Overload 2可以将其rvalue参数仅转换为xvalue(为了解决重载问题,它将被解释为rvalue)。

过载2用于N2951中标记为“B.应将右值作为右值转发”的情况 简而言之,这种情况可以:

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

你不确定u.get()返回左值或右值,但是如果T不是左值引用类型,你想要移动返回的值。 但你不使用std::move ,因为如果T 一个左值引用类型,你不想从返回移动。

我知道这听起来有点做作。 然而, N2951在设置激励用例方面遇到了很大的麻烦,因为std::forward应该使用显式提供的模板参数的所有组合以及普通参数的隐式提供的表达式类别来表现。

这不是一个简单的阅读,但模板和普通参数的每个组合对std::forward基本原理是在N2951中 当时这对委员会来说是有争议的,而不是轻易卖出。

std::forward的最终形式并不完全是N2951提出的。 然而,它确实通过了N2951中提出的所有六个测试。

为什么调用没有绑定到带有左值的std::forward的重载?

它确实如此,但是std::forward 并没有推断出它的模板参数,你告诉它它是什么类型,这就是魔术发生的地方。 你将prvalue传递给f()所以f()推导出[T = int] 然后它调用forward的lvalue重载,并且由于引用折叠 ,在forward中发生的返回类型和static_cast<T&&>将是int&&类型,因此调用void g(int&&)重载。

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

int x = 0;
f(x);

f()推导出[T = int&] ,再次调用forward lvalue重载,但这次返回类型和static_cast<T&&>都是int& ,同样是因为参考折叠规则。 然后,这将调用void g(int&)重载。

现场演示


霍华德已经对你的问题有了一个很好的答案,为什么需要rvalue forward ,但我会添加一个人为的例子来说明这两个版本在起作用。

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

假设你有一个类型foo ,它有一对ref限定的get()成员函数重载。 带有&&限定符的那个返回一个int而另一个返回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

现场演示

std :: forward如何获得正确的参数?

为了完美转发,作为您的代码:

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

请注意:std :: forward需要函数参数和模板类型参数。 模板参数T将编码传递给param的参数是左值还是右值 ,然后转发使用它。 在这种情况下,无论x是什么, x本身是左值,选择重载1, x是转发(通用)参考参数。规则如下:

  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 lvalue of type是stringstring& ,对于转发函数_Tp将是string&__t将是string & & _Tp&&等于string&_Tp&&将是string& && _Tp&&等于string& 。(引用折叠)
  • 如果传递函数f rvalue of type是stringstring&& ,对于forward函数_Tp将是string__t将是string&_Tp&&将是string&&

功能有什么作用?

  • 如果rvalue为rvalue(Overload 2)或lvalue为lvalue(Overload 1),则无需执行任何操作,只需返回。
  • 如果你不小心或别的什么,将rvalue引导到左值,通过static_assert给你一个编译错误。(重载2)
  • 如果你对左值(超载1)进行左值,它与std :: move相同,但不方便。

正如Scott Meyers所说:

鉴于std :: move和std :: forward都归结为强制转换,唯一的区别是std :: move总是强制转换,而std :: forward有时候只会这样做,你可能会问我们是否可以省去std :: move并且只是在任何地方使用std :: forward。 从纯粹的技术角度来看,答案是肯定的:std :: forward可以做到这一切。 std :: move不是必需的。 当然,这两种功能都不是必需的,因为我们可以在任何地方写出演员,但我希望我们同意那将是,令人讨厌的。

什么是右值超载?

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

过载2用于N2951中标记为“B.应将右值作为右值转发”的情况。 简而言之,这种情况可以:

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

你不确定u.get()是否返回左值或右值,但是如果T不是左值引用类型,你想要移动返回的值。 但是你不使用std :: move,因为如果T是左值引用类型,你不想从返回移动。

总之,它允许我们使用T的类型来决定是否移动。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM