When defining a template function taking a function as parameter, what is the difference between taking the function parameter by value and by universal reference ? (i.e) :
template <typename Fn,typename... Args>
void apply(Fn&& fn, Args &&... args);
and
template <typename Fn,typename... Args>
void apply(Fn fn, Args &&... args);
Or maybe the question is : should the function template parameter be an universal reference to maximize genericity ? Or give example where both signature would give different behaviour.
CodePudding user response:
If you only pass functions, I believe there's little difference. In both cases, function will be first converted to function pointers.
If you're to pass a FunctionObject, like a lambda or std::function
, things will become different. There'll be a copy if call by value. Sometimes maybe it's not what you expect.
e.g.
struct Callable {
...
Callable(const& Callable); // maybe expensive to copy
void operator()();
};
template <typename Fn,typename... Args>
void apply_v(Fn fn, Args &&... args);
template <typename Fn,typename... Args>
void apply_ur(Fn&& fn, Args &&... args);
Callable c;
// do something to c
apply_v(c); // may be less effective
apply_ur(c);
apply_ur(std::move(c));
But it really depends on your situation, I believe.
Maybe you can take a look at the signiture in STL, it seems they tend to take a universal reference.
e.g. std::bind
template< class F, class... Args >
constexpr /*unspecified*/ bind( F&& f, Args&&... args );
or construtor of std::thread
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
CodePudding user response:
Most general considerations for type of arguments apply to passing callables as well. I refer you to the core guidelines https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#fcall-parameter-passing.
Or give example where both signature would give different behaviour.
Consider that operator()
can be overloaded based on l/r-valueness:
#include <utility>
#include <iostream>
struct foo {
void operator()() const & { std::cout << "hello "; }
void operator()() && { std::cout << "world\n";}
};
template <typename F> void apply1(F&& f) { std::forward<F>(f)(); }
template <typename F> void apply2(F f) { f(); }
int main() {
foo f;
std::cout << "apply1\n";
apply1(f);
apply1(foo{});
std::cout << "apply2\n";
apply2(f);
apply2(foo{});
}
apply1
hello world
apply2
hello hello