std::forward>(f) 的含义
我对 https://koturn.hatenablog.com 中编写的代码有疑问/条目/2018/06/10/060000
当我传递左值引用时,如果我不使用 std::decay_t 删除引用,则会收到错误。
这是错误消息
'错误:'operator()'不是'main()::
的成员
我不明白为什么需要排除左值引用。
我想知道这个错误是什么意思。
#include <iostream>
#include <utility>
template <typename F>
class
FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f) noexcept
: F{std::forward<F>(f)}
{}
template <typename... Args>
constexpr decltype(auto)
operator()(Args&&... args) const
{
return F::operator()(*this, std::forward<Args>(args)...);
}
}; // class FixPoint
namespace
{
template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}
} // namespace
int
main()
{
auto body = [](auto f, int n) -> int {
return n < 2 ? n : (f(n - 1) + f(n - 2));
};
auto result = makeFixPoint(body)(10);
std::cout << result << std::endl;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
当您将左值 lambda 传递给
makeFixPoint()
时,makeFixPoint()
的模板参数F
会实例化为L&
,其中L
是 lambda 类型。在函数体中,FixPoint>{...}
将被实例化为FixPoint{...}
,因此FixPoint 的构造函数被实例化为接受 lambda 类型的右值引用。在
makeFixPoint()
中,如果使用{std::forward(f)}
初始化它,即{std::forward (f)}
,f
将作为左值转发,这将是格式错误的,因为右值引用无法绑定左值。使用
{std::forward>(f)}
的目的是强制f
作为右值转发。When you pass an lvalue lambda into
makeFixPoint()
, the template parameterF
ofmakeFixPoint()
is instantiated asL&
, whereL
is the lambda type. In the function body,FixPoint<std::decay_t<F>>{...}
will be instantiated asFixPoint<L>{...}
, so the constructor ofFixPoint
is instantiated aswhich accepts a lambda type of rvalue reference. In
makeFixPoint()
, if you initialize it with{std::forward<F>(f)}
i.e.{std::forward<L&>(f)}
,f
will be forwarded as an lvalue, which will be ill-formed since rvalue reference cannot bind an lvalue.The purpose of using
{std::forward<std::decay_t<F>>(f)}
is to forcef
to be forwarded as an rvalue.,您分享的代码有毒。
这意味着我们期望
F
是一个值类型(因为从引用继承是不可能的)。此外,我们将只接受右值——我们将仅从另一个F
移动。这个 std::forward 是没有意义的;这表明编写此代码的人认为他们是完美的转发:但事实并非如此。唯一合法的类型
F
是值类型(不是引用),如果F
是值类型F&&
始终是右值引用,因此std::forward
始终等同于std::move
。在某些情况下,您可能希望这样做
,甚至
上述代码与某些用例相似,但它不是这些用例之一。 (这里的区别是
X
或X&&
是成员的类型,而不是基类;基类不能是引用)。wrap2
的用途是当您想要“延长”右值的生命周期,并且只需引用左值时。wrap1
的使用是当您想要继续完美转发某些表达式时(wrap1
样式对象通常不安全地保留超过一行代码;< code>wrap2 是安全的,只要它们不超过传递给它们的任何左值)。好的,这里有更多危险信号。
inline constexpr
是无意义的标志;constexpr
函数始终是内联
。可能有些编译器将额外的内联视为有意义的内容,因此不能保证出现问题。std::forward
是一个有条件的移动。如果有右值或值类型传递给它,它就会移动。decay
保证返回值类型。所以我们只是扔掉了操作的条件部分,并将其变成了一个原始的动作。这是一个更简单的,但具有相同含义的。
此时您可能会看到危险:
我们无条件地放弃
makeFixPoint
参数。名称makeFixPoint
中没有任何内容表明“我从参数中移动”,并且它接受转发引用;所以它会默默地消耗一个左值并从中移动。这是非常粗鲁的。
因此,代码的合理版本是:
[SNIP]
,它可以稍微清理一下内容。
,The code you have shared is toxic.
what this means is that we expect
F
to be a value type (because inheriting from a reference isn't possible). In addition, we will only accept rvalues -- we will only move from anotherF
.this
std::forward<F>
is pointless; this indicates that the person writing this code thinks they are perfect forwarding: they are not. The only legal typesF
are value types (not references), and ifF
is a value typeF&&
is always an rvalue reference, and thusstd::forward<F>
is always equivalent tostd::move
.There are cases where you want to
or even
so the above code is similar to some use cases, but it isn't one of those use cases. (The difference here is that
X
orX&&
is the type of a member, not a base class; base classes cannot be references).The use for
wrap2
is when you want to "lifetime extend" rvalues, and simply take references to lvalues. The use forwrap1
is when you want to continue the perfect forwarding of some expression (wrap1
style objects are generally unsafe to keep around for more than a single line of code;wrap2
are safe so long as they don't outlive any lvalue passed to them).Ok more red flags here.
inline constexpr
is a sign of nonsense;constexpr
functions are alwaysinline
. There could be some compilers who treat the extrainline
as meaning something, so not a guarantee of a problem.std::forward
is a conditional move. It moves if there is an rvalue or value type passed to it.decay
is guaranteed to return a value type. So we just threw out the conditional part of the operation, and made it into a raw move.this is a simpler one that has the same meaning.
At this point you'll probably see the danger:
we are unconditionally moving from the
makeFixPoint
argument. There is nothing about the namemakeFixPoint
that says "I move from the argument", and it accept forwarding references; so it will silently consume an lvalue and move-from it.This is very rude.
So a sensible version of the code is:
[SNIP]
that cleans things up a bit.
这样模板就无法按预期工作。
首先,因为它应该可以从
F
继承,所以使用std::decay_t
没有多大意义。相反,它可能应该只是 std::remove_cvref_t ,但在 C++20 之前,编写起来有点麻烦,而 std::decay_t 的作用几乎相同。在类模板中
F
旨在成为非引用类型。它不是构造函数的模板参数。所以它不能用于完美转发。构造函数参数始终是不能采用左值的右值引用。即使将左值传递给
makeFixPoint
,调用std::forward>(f)
也会强制转换为右值引用,这会导致然后可以在构造函数中使用。这显然是危险的。该函数始终的行为就好像您已将参数std::move
放入其中一样。它应该只是 std::forward(f) 并且构造函数应该采用另一个可用于形成转发引用的模板参数:
The template just can't work as intended this way.
First, because it is supposed to be possible to inherit from
F
, it doesn't make much sense to usestd::decay_t
. Instead it is probably supposed to be juststd::remove_cvref_t
, but before C++20 that was kind of cumbersome to write andstd::decay_t
does almost the same.In the class template
F
is intended to be a non-reference type. It is not a template parameter of the constructor. So it cannot be used for perfect-forwarding. The constructor argument will always be a rvalue reference which cannot take lvalues.Even if a lvalue is passed to
makeFixPoint
, the callstd::forward<std::decay_t<F>>(f)
forces conversion to a rvalue reference, which then can be used in the constructor. That is clearly dangerous. The function always behaves as if you hadstd::move
d the argument into it.It should just be
std::forward<F>(f)
and the constructor should take another template parameter that can be used to form a forwarding reference: