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;
它确实绑定到std::forward
取左值的重载:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
它与T == int
绑定。 此函数指定返回:
static_cast<T&&>(t)
因为f
T
推断出int
. 因此,此重载将左值int
转换为 xvalue:
static_cast<int&&>(t)
因此调用g(int&&)
重载。
总之,std::forward
的左值重载可能会将其参数转换为左值或右值,具体取决于调用它的T
类型。
std::forward
的右值重载只能转换为右值。 如果尝试调用该重载并强制转换为 lvalue,则程序格式不正确(需要编译时错误(。
所以重载 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;
捕获右值(即 X 值和 pr值(。
重载 1 可以将其左值参数强制转换为左值或 x值(后者将被解释为用于重载解析目的的右值(。
重载2 可以仅将其右值参数强制转换为 x值(出于重载解析目的,该参数将被解释为 右值(。
重载 2 适用于 N2951 中标记为"B.应将右值转发为右值"的情况。 简而言之,这种情况可以实现:
std::forward<T>(u.get());
不确定u.get()
返回左值还是右值,但无论哪种方式,如果T
不是左值引用类型,您都希望移动返回的值。 但是您不使用 std::move
因为如果 T
是左值引用类型,则您不希望从返回中移动。
我知道这听起来有点做作。 然而,N2951 在设置激励用例方面遇到了很大的麻烦,说明std::forward
应该如何使用显式提供的模板参数和普通参数的隐式提供的表达式类别的所有组合。
这不是一个容易阅读的,但模板和普通参数的每种组合的基本原理是 N2951 std::forward
。 当时,这在委员会中是有争议的,而且不容易出售。
std::forward
的最终形式并不完全是N2951提出的。 但是,它确实通过了N2951中提供的所有六项测试。
为什么调用不绑定到采用左值的
std::forward
重载?
它确实这样做了,但std::forward
不会推断它的模板参数,你告诉它它是什么类型,这就是魔术发生的地方。您正在将 prvalue 传递给f()
以便f()
推断出[T = int]
.然后它调用 forward
的左值重载,并且由于引用折叠,返回类型和static_cast<T&&>
forward
内发生的将属于 int&&
类型,从而调用void g(int&&)
重载。
如果要将左值传递给f()
int x = 0;
f(x);
f()
推导出[T = int&]
,再次调用forward
的相同左值重载,但这次返回类型和static_cast<T&&>
都是int&
的,同样是因为引用折叠规则。然后,这将改为调用void g(int&)
重载。
现场演示
霍华德已经对你关于为什么需要 forward
的右值重载的问题有一个很好的答案,但我会添加一个人为的例子来显示两个版本的运行情况。
这两个重载背后的基本思想是,在您传递给forward
的表达式的结果产生右值(prvalue 或 xvalue(的情况下,将调用右值重载。
假设您有一个类型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));
}
请注意,this:std::forward 需要函数参数和模板类型参数。 模板参数 T 将编码传递给 param 的参数是左值还是右值,然后转发使用它。在这种情况下,不管x
引用什么,x
本身就是一个左值,选择重载1,x是转发(通用(引用参数。规则如下:
- 如果
f
参数的 expr 是左值,则x
和T
都将使用左值引用类型 - 如果
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 类型的左值为
string
或string&
,则前向函数_Tp
将被string&
,__t
将被string & &
等于string&
,_Tp&&
将string& &&
等于string&
.(参考资料折叠( - 如果传递函数 f 类型的右值为
string
或string&&
,则前向函数_Tp
将被string
,__t
string&
,_Tp&&
将被string&&
。
函数有什么作用?
- 如果右值到右值(重载
- 2(或左值到左值(重载1(,没什么可做的,只是返回。
- 如果你不小心或其他什么,将右值引导到左值,通过
static_assert
给你一个编译错误。(过载 2( - 如果你做左值到右值(重载1(,它与std::move相同,但不方便。
正如斯科特·迈耶斯(Scott Meyers(所说:
鉴于 std::move 和 std::forward 都归结为强制转换,因此 唯一的区别是 std::move 总是投射,而 std::forward 只是有时这样做,你可能会问我们是否可以 省去std::move,只在任何地方使用std::forward。从一个 纯技术角度,答案是肯定的:std::forward can do 这一切。std::move不是必需的。当然,这两个函数都不是 真的很有必要,因为我们可以在任何地方写演员表,但我 希望我们同意那会,好吧,很糟糕。
右值重载有什么用?
我不确定,它应该在你真正需要的地方使用。如霍华德·欣南特 说:
重载 2 适用于标记为"B. 应将右值转发为 右值"在 N2951 中。简而言之,这种情况可以实现:
std::forward<T>(u.get());
您不确定 you.get(( 是否返回左值或右值,但是 无论哪种方式,如果 T 不是左值引用类型,您都希望移动 返回值。但是你不使用 std::move,因为如果 T 是一个左值 引用类型,则您不想从返回中移动。
总之,它允许我们使用 T 的类型来决定是否移动。
- visual是否可以在c++中创建一个接收无限数量相同类型(或至少相当数量)参数的函数
- 使用模板化的键类型定义 std::map,该键类型基于作为参数接收的函数
- 标准对此指向成员函数类型模板参数有何说明?是我的代码有误,还是 MSVS 16.6 有问题?
- 使用指针访问参数接收的结构中的元素时内存泄漏
- 用于接收通用地图作为参数的模板函数
- 调用作为参数接收的可变参数模板的可变参数模板
- 接收参数的 lambda 函数的类型
- 如何在C++中接收任何类型的函数参数并获取函数内传递变量的类型?
- 为什么不能将std :: async用于接收对抽象类作为参数的函数
- 接收迭代器作为参数并知道类
- 重构许多函数以优雅地接收任何类型的参数(模板<any>)
- 声明一个模板函数,该函数接收两个泛型迭代器作为参数
- 接收std :: Pair作为参数并从支撑列表初始化中推论的模板函数
- 派生运算符<接收 Base 对象作为参数
- C 接收const lvalue和rvalue参考参数,而无需过载
- 我想意识到一个接收STD ::相同参数的函数,然后称其为“如何”
- 接收DQUE或向量作为C 函数中的共同参数
- 链接错误:无法弄清楚如何将数组作为函数参数传递/接收
- "return-by-reference"或"pass-by-reference"参数何时与constexpr兼容?
- Qt中的拖放问题:传递参数到接收dropEvent