Home > Mobile >  Meaning of std::forward<std::decay_t<F>>(f)
Meaning of std::forward<std::decay_t<F>>(f)

Time:03-10

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

CodePudding user response:

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.

CodePudding user response:

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)}
{}
  • Related