std::forward>(f) 的含义

发布于 2025-01-13 04:14:49 字数 1232 浏览 0 评论 0 原文

我对 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;
}

I have a question about the code written in https://koturn.hatenablog.com/entry/2018/06/10/060000
When I pass a left value reference, if I do not remove the reference with std::decay_t, I get an error.
Here is the error message
'error: 'operator()' is not a member of 'main()::<lambda(auto:11, int)>&
I don't understand why it is necessary to exclude the left value reference.
I would like to know what this error means.

#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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

饮惑 2025-01-20 04:14:49

我不明白为什么需要排除左值
参考。我想知道这个错误是什么意思。

当您将左值 lambda 传递给 makeFixPoint() 时,makeFixPoint() 的模板参数 F 会实例化为L&,其中 L 是 lambda 类型。在函数体中,FixPoint>{...} 将被实例化为 FixPoint{...},因此FixPoint 的构造函数被实例化为

explicit constexpr FixPoint(L&& f);

接受 lambda 类型的右值引用。在 makeFixPoint() 中,如果使用 {std::forward(f)} 初始化它,即 {std::forward (f)}f 将作为左值转发,这将是格式错误的,因为右值引用无法绑定左值

使用 {std::forward>(f)} 的目的是强制 f 作为右值转发。

I don't understand why it is necessary to exclude the left value
reference. I would like to know what this error means.

When you pass an lvalue lambda into makeFixPoint(), the template parameter F of makeFixPoint() is instantiated as L&, where L is the lambda type. In the function body, FixPoint<std::decay_t<F>>{...} will be instantiated as FixPoint<L>{...}, so the constructor of FixPoint is instantiated as

explicit constexpr FixPoint(L&& f);

which 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 force f to be forwarded as an rvalue.

〆一缕阳光ご 2025-01-20 04:14:49

,您分享的代码有毒。

template <typename F>
class FixPoint : private F
{
public:
  explicit constexpr FixPoint(F&& f)

这意味着我们期望 F 是一个值类型(因为从引用继承是不可能的)。此外,我们将只接受右值——我们将从另一个F移动。

  : F{std::forward<F>(f)}
{}

这个 std::forward 是没有意义的;这表明编写此代码的人认为他们是完美的转发:但事实并非如此。唯一合法的类型 F 是值类型(不是引用),如果 F 是值类型 F&& 始终是右值引用,因此 std::forward 始终等同于 std::move

在某些情况下,您可能希望这样做

template<class X>
struct wrap1 {
  X&& x;
  wrap1(X&& xin):x(std::forward<X>(xin)){}
};

,甚至

template<class X>
struct wrap2 {
  X x;
  wrap2(X&& xin):x(std::forward<X>(xin)){}
};

上述代码与某些用例相似,但它不是这些用例之一。 (这里的区别是 XX&& 是成员的类型,而不是基类;基类不能是引用)。

wrap2 的用途是当您想要“延长”右值的生命周期,并且只需引用左值时。 wrap1 的使用是当您想要继续完美转发某些表达式时(wrap1 样式对象通常不安全地保留超过一行代码;< code>wrap2 是安全的,只要它们不超过传递给它们的任何左值)。


template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
  return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}

好的,这里有更多危险信号。 inline constexpr 是无意义的标志; constexpr 函数始终是内联。可能有些编译器将额外的内联视为有意义的内容,因此不能保证出现问题。

return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};

std::forward 是一个有条件的移动。如果有右值或值类型传递给它,它就会移动。 decay 保证返回值类型。所以我们只是扔掉了操作的条件部分,并将其变成了一个原始的动作。

return FixPoint<std::decay_t<F>>{std::move(f)};

这是一个更简单的,但具有相同含义的。

此时您可能会看到危险:

template <typename F>
constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
  return FixPoint<std::decay_t<F>>{std::move(f)};
}

我们无条件地放弃 makeFixPoint 参数。名称 makeFixPoint 中没有任何内容表明“我从参数中移动”,并且它接受转发引用;所以它会默默地消耗一个左值并从中移动。

这是非常粗鲁的。


因此,代码的合理版本是:

template <typename F>
class FixPoint : private F
{
public:
  template<class U,
    class dU = std::decay_t<U>,
    std::enable_if_t<!std::is_same_v<dU, FixPoint> && std::is_same_v<dU, F>, bool> = true
  >
  explicit constexpr FixPoint(U&& u) noexcept
    : F{std::forward<U>(u)}
  {}

[SNIP]

namespace
{
  template <typename F>
  constexpr FixPoint<std::decay_t<F>>
  makeFixPoint(F&& f) noexcept
  {
    return FixPoint<std::decay_t<F>>{std::forward<F>(f)};
  }
}  // namespace

,它可以稍微清理一下内容。

,The code you have shared is toxic.

template <typename F>
class FixPoint : private F
{
public:
  explicit constexpr FixPoint(F&& f)

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 another F.

  : F{std::forward<F>(f)}
{}

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 types F are value types (not references), and if F is a value type F&& is always an rvalue reference, and thus std::forward<F> is always equivalent to std::move.

There are cases where you want to

template<class X>
struct wrap1 {
  X&& x;
  wrap1(X&& xin):x(std::forward<X>(xin)){}
};

or even

template<class X>
struct wrap2 {
  X x;
  wrap2(X&& xin):x(std::forward<X>(xin)){}
};

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 or X&& 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 for wrap1 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).


template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
  return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}

Ok more red flags here. inline constexpr is a sign of nonsense; constexpr functions are always inline. There could be some compilers who treat the extra inline as meaning something, so not a guarantee of a problem.

return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};

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.

return FixPoint<std::decay_t<F>>{std::move(f)};

this is a simpler one that has the same meaning.

At this point you'll probably see the danger:

template <typename F>
constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
  return FixPoint<std::decay_t<F>>{std::move(f)};
}

we are unconditionally moving from the makeFixPoint argument. There is nothing about the name makeFixPoint 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:

template <typename F>
class FixPoint : private F
{
public:
  template<class U,
    class dU = std::decay_t<U>,
    std::enable_if_t<!std::is_same_v<dU, FixPoint> && std::is_same_v<dU, F>, bool> = true
  >
  explicit constexpr FixPoint(U&& u) noexcept
    : F{std::forward<U>(u)}
  {}

[SNIP]

namespace
{
  template <typename F>
  constexpr FixPoint<std::decay_t<F>>
  makeFixPoint(F&& f) noexcept
  {
    return FixPoint<std::decay_t<F>>{std::forward<F>(f)};
  }
}  // namespace

that cleans things up a bit.

吻泪 2025-01-20 04:14:49

这样模板就无法按预期工作。

首先,因为它应该可以从F继承,所以使用std::decay_t没有多大意义。相反,它可能应该只是 std::remove_cvref_t ,但在 C++20 之前,编写起来有点麻烦,而 std::decay_t 的作用几乎相同。

在类模板中 F 旨在成为非引用类型。它不是构造函数的模板参数。所以它不能用于完美转发。构造函数参数始终是不能采用左值的右值引用。

即使将左值传递给 makeFixPoint,调用 std::forward>(f) 也会强制转换为右值引用,这会导致然后可以在构造函数中使用。这显然是危险的。该函数始终的行为就好像您已将参数std::move放入其中一样。

它应该只是 std::forward(f) 并且构造函数应该采用另一个可用于形成转发引用的模板参数:

template<typename G>
requires std::is_same_v<std::remove_cvref_t<G>, F>
explicit constexpr FixPoint(G&& g) noexcept
  : F{std::forward<G>(g)}
{}

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 use std::decay_t. Instead it is probably supposed to be just std::remove_cvref_t, but before C++20 that was kind of cumbersome to write and std::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 call std::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 had std::moved 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:

template<typename G>
requires std::is_same_v<std::remove_cvref_t<G>, F>
explicit constexpr FixPoint(G&& g) noexcept
  : F{std::forward<G>(g)}
{}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文