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::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:
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)}
{}