Mr.C64 Mr.C64 - 21 days ago 6
C++ Question

Best way of passing callback function parameters in C++

What is the best way of passing a callback function parameter in C++?

I thought of simply using templates, like this:

template <typename Function>
void DoSomething(Function callback)

This is the way used e.g. in
for the comparison function object.

What about passing using
? E.g.:

template <typename Function>
void DoSomething(Function&& callback)

What are the pros and cons of those two methods, and why does the STL uses the former e.g. in



template <typename Function>
void DoSomething(Function&& callback)

… which uses a forwarding reference to pass by reference, is IMHO superior for the case where the function just uses the callback. Because a functor object, although usually small, can be arbitrarily large. Passing it by value then incurs some needless overhead.

The formal argument can end up as T&, T const& or T&& depending on the actual argument.

On the other hand, passing a simple function pointer by reference involves an extra needless indirection, which in principle could mean some slight overhead.

If in doubt whether this is significant for a given compiler, system and application, then measure.


why does the STL uses the former [passing by value] e.g. in std::sort?

… it's worth noting that std::sort has been there since C++98, well before forwarding references were introduced in C++11, so it could not have that signature originally.

Possibly there just has not been sufficient incentive to improve this. After all, one should generally not fix that which works. Still, extra oveloads with an “execution policy” argument are introduced in C++17.

Since this concerns a possible C++11 change, it's not covered by the usual source for rationales, namely Bjarne Stroustrup's “The design and evolution of C++”., and I do not know of any conclusive answer.

Using the template<class F> void call( F ) style, you can still get back "pass by reference". Simply do call( std::ref( your callback ) ). std::ref overrides operator() and forwards it to the contained object.

Simularly, with template<class F> void call( F&& ) style, you can write:

template<class T>
std::decay_t<T> copy( T&& t ) { return std::forward<T>(t); }

which explicitly copies something, and force call to use a local copy of f by:

call( copy(f) );

so the two styles mainly differ in how they behave by default.