Home > Blockchain >  Function signature for high order function
Function signature for high order function

Time:06-28

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

Output:

apply1
hello world
apply2
hello hello 
  • Related