As usual, this depends on how good your compiler is today, and how good it will be in the future.
Currently, compilers are not very good at optimizing std::function
. Surprisingly, std::function
is a complicated object that sometimes has to allocate memory to maintain stateful lambda functions. It also complicates matters that std::function
has to be able to refer to member function, regular functions, and lambdas, and do it in a transparent manner. This transparency has a hefty runtime cost.
So, if you want the fastest possible code, you should be careful with std::function
. For that reason the first variant is the fastest (on today's compilers):
int functor_by_value(functor_type x) {
return x(1);
}
It simply passes a pointer to a function.
When stateful lambdas are involved you have only two options. Either pass the lambda as a template argument, or convert to std::function
. Hence, if you want the fastest code possible with lambdas (in today's compilers), you'd pass the function as a templated argument.
Since a lambda function may have a big state, passing it around may copy the big state (when copy elision is not possible). GCC will construct the lambda directly on the parameter list (with no copy), but a nested function will invoke a copy constructor for the lambda. To avoid that, either pass it by const reference (in that case it can't be mutable), or by rvalue reference:
template<class Func>
void run2(const Func & f)
{
std::cout << "Running\n";
f();
}
template<class Func>
void run(const Func & f)
{
run2(f);
}
int main()
{
run([s=BigState()]() { std::cout << "apply\n"; });
return 0;
}
Or:
template<class Func>
void run2(Func && f)
{
f();
}
template<class Func>
void run(Func && f)
{
run2(std::forward<Func>(f));
}
int main()
{
run([s=BigState()]() { std::cout << "apply\n"; });
return 0;
}
Without using references, the BigState() will be copied when the lambda is copied.
UPDATE:
After reading the question again I see that it wants to restrict the signature
template<typename Func,
typename = std::enable_if_t<
std::is_convertible_v<decltype(Func(1)), int>>>
void run2(const Func & f)
{
std::cout << "Running\n";
f();
}
This will restrict it to any function that can accept int
(possibly with an implicit cast), and returns an int
or any type that is implicitly cast to int
. However, if you want to accept only function-like objects that accept exactly int
and return exactly int you can see if the lambda is convertible to std::function<int(int)>