...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Generic libraries that accept callable arguments are common in C++. Accepting
a callable argument of builin type often involves a lot of repetitive code
because the accepting function is overloaded for different function arities.
Further, member functions may have const
/volatile
-qualifiers,
a function may take a variable number of (additional, POD-typed) arguments
(such as printf
) and several C++ implementations encode
a calling convention with each function's type to allow calls across language
or (sub-)system boundaries.
template<typename R> void accept_function(R(* func)()); template<typename R> void accept_function(R(& func)()); template<typename R, typename C> void accept_function(R(C::* func)()); template<typename R, typename C> void accept_function(R(C::* func)() const); template<typename R, typename C> void accept_function(R(C::* func)() volatile); template<typename R, typename C> void accept_function(R(C::* func)() const volatile); template<typename R> void accept_function(R(* func)(...)); template<typename R> void accept_function(R(& func)(...)); template<typename R, typename C> void accept_function(R(C::* func)(...)); template<typename R, typename C> void accept_function(R(C::* func)(...) const); template<typename R, typename C> void accept_function(R(C::* func)(...) volatile); template<typename R, typename C> void accept_function(R(C::* func)(...) const volatile); // ... // needs to be repeated for every additional function parameter // times the number of possible calling conventions
The "overloading approach" obviously does not scale well: There might be several functions that accept callable arguments in one library and client code might end up using several libraries that use this pattern. On the developer side, library developers spend their time solving the same problem, working around the same portability issues, and apply similar optimizations to keep the compilation time down.
Using Boost.FunctionTypes it is possible to write a single function template instead:
template<typename F> void accept_function(F f) { // ... use Boost.FunctionTypes to analyse F }
The combination with a tuples library that provides an invoker component, such as Boost.Fusion, allows to build flexible callback facilities that are entirely free of repetitive code as shown by the interpreter example.
When taking the address of an overloaded function or function template, the
type of the function must be known from the context the expression is used
in. The code below shows three examples for choosing the float(float)
overload of std::abs
.
float (*ptr_absf)(float) = & std::abs; void foo(float(*func)(float)); void bar() { foo(& std::abs); } std::transform(b, e, o, static_cast<float(*)(float)>(& std::abs));
The library's type synthesis capabilities can be used to automate overload selection and instantiation of function templates. Given an overloaded function template
template<typename R, typename T0> R overloaded(T0); template<typename R, typename T0, typename T1> R overloaded(T0,T1); template<typename R. typename T0, typename T1, typename T2> R overloaded(T0,T1,T2);
we can pick any of the three overloads and instantiate the template with template arguments from a type sequence in a single expression:
static_cast<function_pointer<Seq>::type>(& overloaded)
This technique can be occasionally more flexible than template argument deduction from a function call because the exact types from the sequence are used to specialize the template (including possibly cv-qualified reference types and the result type). It is applied twice in the interface example.
Another interersting property of callable, builtin types is that they can be valid types for non-type template parameters. This way, a function can be pinpointed at compile time, allowing the compiler to eliminate the call by inlining. The fast_mem_fn example exploits this characteristic and implements a potentially inlining version of boost::mem_fn limited to member functions that are known at compile time.