Zizheng Tai
Zizheng Tai

Reputation: 6636

std::invoke - perfect forwarding functor

Trying to understand why the following example fails to compile:

#include <functional>

template <typename F>
void f1(F&& f)
{
  std::forward<F>(f)("hi");
}

template <typename F>
void f2(F&& f)
{
  std::invoke(f, "hi");  // works but can't perfect forward functor
}

template <typename F>
void f3(F&& f)
{
  std::invoke<F>(f, "hi");
}

int main()
{
  f1([](const char*) {});  // ok
  f2([](const char*) {});  // ok
  f3([](const char*) {});  // error
}

cppreference says the following about std::invoke:

  1. Invoke the Callable object f with the parameters args. As by INVOKE(std::forward<F>(f), std::forward<Args>(args)...). This overload participates in overload resolution only if std::is_invocable_v<F, Args...> is true.

So why is f3 not equivalent to f1?

Upvotes: 4

Views: 359

Answers (3)

xskxzr
xskxzr

Reputation: 13040

std::invoke is itself a function. In your case, its first parameter is a rvalue reference while f is a lvalue, so the error occurs.

INVOKE(std::forward<F>(f), std::forward<Args>(args)...) is executed after the function std::invoke is properly selected and called. Basically, your lambda function is passed as follows:

original lambda in main -> the parameter of f3 -> the parameter of std::invoke -> the parameter of INVOKE

So the std::forward in INVOKE(std::forward<F>(f), std::forward<Args>(args)...) is used in the last step, while you need to forward the lambda in the middle step (the parameter of f3 -> the parameter of std::invoke). I guess this is where your confusion comes.

Upvotes: 3

debashish.ghosh
debashish.ghosh

Reputation: 123

I guess you're getting this error:

note: template argument deduction/substitution failed:
note: cannot convert ‘f’ (type ‘main()::<lambda(const char*)>’) to type ‘main()::<lambda(const char*)>&&’

That's because inside the function f3, f is an L-value, but the invoke expects an R-value. For template argument deduction/substitution to work, the types have to match EXACTLY.

When you perfect forward f to invoke, this issue is resolved as you passed an R-value originally from outside f3.

Upvotes: 0

Patrick Roberts
Patrick Roberts

Reputation: 51916

Because you need to std::forward<F>(f) to std::invoke():

template <typename F>
void f3(F&& f)
{
  std::invoke<F>(std::forward<F>(f), "hi"); // works
}

Consider the difference between these two calls:

void f(const int&) { std::cout << "const int&" << std::endl; }
void f(int&&) { std::cout << "int&&" << std::endl; }

int main()
{
  std::cout << "first" << std::endl;
  int&& a = 3;
  f(a);
  std::cout << "second" << std::endl;
  int&& b = 4;
  f(std::forward<int>(b));
}

The output is

first
const int&
second
int&&

If you remove the const int& overload, you even get a compiler error for the first call:

error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'

The std::forward() is necessary for passing the correct type to std::invoke().

Upvotes: 2

Related Questions