为什么 std::forward 禁用模板参数推导?
在 VS2010 中,std::forward 的定义如下:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity
似乎仅用于禁用模板参数推导。在这种情况下故意禁用它有什么意义?
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity
appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果将对类型
X
的对象的右值引用传递给采用类型T&&
作为其参数的模板函数,模板参数推导会推导出T< /code> 为
X
。因此,该参数的类型为X&&
。如果函数参数是左值或 const 左值,则编译器将其类型推导为该类型的左值引用或 const 左值引用。如果
std::forward
使用模板参数推导:因为
具有名称的对象是左值
,这是唯一一次std::forward7
或func()
)时, > 将正确转换为T&&
。在完美转发的情况下,传递给 std::forward 的 arg 是一个左值,因为它有一个名称。std::forward
的类型将被推导为左值引用或 const 左值引用。引用折叠规则会导致 std::forward 中static_cast(arg)
中的T&&
始终解析为左值引用或 const左值参考。示例:
If you pass an rvalue reference to an object of type
X
to a template function that takes typeT&&
as its parameter, template argument deduction deducesT
to beX
. Therefore, the parameter has typeX&&
. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.If
std::forward
used template argument deduction:Since
objects with names are lvalues
the only timestd::forward
would correctly cast toT&&
would be when the input argument was an unnamed rvalue (like7
orfunc()
). In the case of perfect forwarding thearg
you pass tostd::forward
is an lvalue because it has a name.std::forward
's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause theT&&
instatic_cast<T&&>(arg)
in std::forward to always resolve as an lvalue reference or const lvalue reference.Example:
因为
std::forward(expr)
没有用。它唯一能做的就是无操作,即完美地转发其参数并像恒等函数一样工作。另一种选择是它与std::move
相同,但我们已经有了。换句话说,假设这是可能的,std::forward(arg)
在语义上等同于arg
。另一方面,std::forward因此,通过禁止
std::forward(arg)
它有助于捕获程序员错误,并且我们不会损失任何东西,因为std::forward(arg)
的任何可能使用都会被简单地替换为 <代码>arg。
我认为,如果我们专注于 std::forward(arg) 的作用,而不是
std::forward 的作用,你会更好地理解事情(arg)
就可以了(因为这是一个无趣的无操作)。让我们尝试编写一个无操作函数模板来完美地转发其参数。这种幼稚的第一次尝试并不完全有效。如果我们调用
noop(0)
,那么NoopArg
就会被推导为int
。这意味着返回类型是 int&& 并且我们无法从表达式 arg 绑定这样的右值引用,它是一个左值(它是一个范围)。如果我们尝试:那么 int i = 0; noop(i); 失败。这次,
NoopArg
被推导为int&
(参考折叠规则保证int& &&
折叠为int&< /code>),因此返回类型为
int&
,这次我们无法从表达式std::move(arg)
绑定这样的左值引用是一个 x 值。在像
noop
这样的完美转发函数的上下文中,有时我们想要移动,但有时我们不想移动。判断是否应该移动的规则取决于Arg
:如果它不是左值引用类型,则意味着noop
传递了一个右值。如果它是左值引用类型,则意味着noop
传递了一个左值。因此,在std::forward(arg)
中,NoopArg
是std::forward
的必要参数> 为了让函数模板做正确的事情。没有它,就没有足够的信息。这个NoopArg
与std::forward
的T
参数不类型相同。一般情况。Because
std::forward(expr)
is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same asstd::move
, but we already have that. In other words, assuming it were possible, instd::forward(arg)
is semantically equivalent toarg
. On the other hand,std::forward<Arg>(arg)
is not a no-op in the general case.So by forbidding
std::forward(arg)
it helps catch programmer errors and we lose nothing since any possible use ofstd::forward(arg)
are trivially replaced byarg
.I think you'd understand things better if we focus on what exactly
std::forward<Arg>(arg)
does, rather than whatstd::forward(arg)
would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.This naive first attempt isn't quite valid. If we call
noop(0)
thenNoopArg
is deduced asint
. This means that the return type isint&&
and we can't bind such an rvalue reference from the expressionarg
, which is an lvalue (it's the name of a parameter). If we then attempt:then
int i = 0; noop(i);
fails. This time,NoopArg
is deduced asint&
(reference collapsing rules guarantees thatint& &&
collapses toint&
), hence the return type isint&
, and this time we can't bind such an lvalue reference from the expressionstd::move(arg)
which is an xvalue.In the context of a perfect-forwarding function like
noop
, sometimes we want to move, but other times we don't. The rule to know whether we should move depends onArg
: if it's not an lvalue reference type, it meansnoop
was passed an rvalue. If it is an lvalue reference type, it meansnoop
was passed an lvalue. So instd::forward<NoopArg>(arg)
,NoopArg
is a necessary argument tostd::forward
in order for the function template to do the right thing. Without it, there's not enough information. ThisNoopArg
is not the same type as what theT
parameter ofstd::forward
would be deduced in the general case.简短回答:
因为
std::forward
要按预期工作(即,成功地传递原始类型信息),它是 >意味着使用内部模板上下文,并且它必须使用从封闭模板上下文推导的类型参数 , 而不是自行推导类型参数(因为只有封闭模板才有机会推导真实的类型信息,这将在详细信息中解释),因此必须提供类型参数。虽然在非模板上下文中使用 std::forward 是可能的,但它是没有意义的(将在详细信息中解释)。
如果有人胆敢尝试实现 std::forward 来允许类型推导,他/她注定会惨痛地失败。
详细信息:
示例:
观察
arg
被声明为T&&
,(这是推导出传递的真实类型的关键,并且)它不是右值引用,尽管它具有相同的语法,但它被称为通用引用(Scott Meyers 创造的术语),因为T
是泛型类型,(同样,在string s; auto && ss = s;
ss 中,ss 不是右值 参考)。感谢通用引用,在实例化
someFunc
时,会发生一些类型推导的魔法,具体如下:_T
或_T &
,传递给someFunc
,T
将被推导为_T &
(,是的,即使类型X
只是_T
,请阅读 Meyers 的文章);_T &&
类型的右值传递给someFunc
,T
将被推导为_T &&
现在,您可以将
T
替换为真实类型在上面的代码中:当传递左值 obj 时:
并且在应用引用折叠规则(后,请阅读Meyers 的文章),我们得到
: obj 被传递:
并且在应用引用折叠规则(,请阅读 Meyers 的文章),我们得到:
现在,你可以猜到 std::forwrd 本质上所做的只是
static_cast(para)
( 事实上,在 clang 中11 的实现是static_cast(para)
,应用引用折叠规则后是一样的)。一切都很顺利。但是如果你考虑让 std::fowrd 自己推导类型参数,你很快就会发现 在
someFunc
中,std::向前
字面上无法推导出arg
的原始类型。如果您尝试让编译器执行此操作,则它永远不会被推导为
_T &&
(,是的,甚至当arg 绑定到
_T &&
,它仍然是
someFunc
内的 lvaule obj,因此只能是推论为_T
或_T &
....你真的应该阅读Meyers 的文章)。最后,为什么你应该只在模板内使用
std::forward
?因为在非模板上下文中,您确切地知道您拥有什么类型的 obj。因此,如果您有一个左值绑定到右值引用,并且您需要将其作为左值传递到另一个函数,只需传递它,或者如果您需要将其作为右值传递,只需执行std::move
。您只需在非模板上下文中不需要 std::forward 即可。Short answer:
Because for
std::forward
to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.Though using
std::forward
inside non-template context is possible, it is pointless(, will be explained in the details).And if anyone dares to try implementing
std::forward
to allow type deducing, he/she is doomed to fail painfully.Details:
Example:
Observer that
arg
is declared asT&&
,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), becauseT
is a generic type, (likewise, instring s; auto && ss = s;
ss is not a rvalue reference).Thanks to universal reference, some type deduce magic happens when
someFunc
is being instantiated, specifically as following:_T
or_T &
, is passed tosomeFunc
,T
will be deduced as_T &
(, yeah, even if the type ofX
is just_T
, please read Meyers' artical);_T &&
is passed tosomeFunc
,T
will be deduced as_T &&
Now, you can replace
T
with the true type in above code:When lvalue obj is passed:
And after applying reference collapse rule(, pls read Meyers' artical), we get:
When rvalue obj is passed:
And after applying reference collapse rule(, pls read Meyers' artical), we get:
Now, you can guess what std::forwrd does eseentially is just
static_cast<T>(para)
(, in fact, in clang 11's implementation it isstatic_cast<T &&>(para)
, which is the same after applying reference collapsing rule). Everything works out fine.But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside
someFunc
,std::forward
literally IS NOT ABLE TO deduce the original type ofarg
.If you try to make the compiler do it, it will never be deduced as
_T &&
(, yeah, even whenarg
is bind to an_T &&
, it is still an lvaule obj insidesomeFunc
, hence can only be deduceed as_T
or_T &
.... you really should read Meyers' artical).Last, why should you only use
std::forward
inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just dostd::move
. You simply DON'T NEED std::forward inside non-template context.