I’ve learned a neat trick today, and I would very much like to share it with you.

Given an anonymous function how do you call it with default values for all the parameters?

For instance, the result of running:

auto res = Invoke([](int a, double b) {
return std::to_string(a) + " " + std::to_string(b);
);


…should be “0 0.000000”.

Of course it should not be limited to just the built-in types, but rather anything with a default constructor.

# First try

At first glance, we can define the signature of Invoke as:

template<typename R, typename... Args>
R Invoke(std::function<R(Args... args)> f);


When dealing with Variadic Templates the general approach is always recursion.

You start by defining the base case for a function taking no arguments at all:

template<typename R>
R Invoke(std::function<R()> f)
{
return f();
}


Then you try to go from function taking n arguments to one taking n+1 arguments:

template<typename R, /* Return type */
typename A, /* Type of first argument */
typename... Args /* n other arguments */ >
// Invoke on a function taking n+1 arguments:
R Invoke(std::function<R(A a, Args...)> f_nPlus1)
{
A defaultA{}; // Prepare a default value for the first argument

// Define a function taking n arguments
std::function<R(Args...)> f_n = [f_nPlus1, defaultA](Args... args1) {
return f_nPlus1(defaultA, args1...); // Call the original f,
// Pass the newly prepared default value for the first argument,
// And just pass all the other arguments -
// recusion will make sure these have all default values
};

// Recurse on f_n:
return Invoke(f_n);
}


We are in good shape, but there is one annoying problem: Invoke works on objects of type std::function, not lambdas. Anonymous functions, aka Lambdas, are convertible to std::function, but the compiler cannot infer this conversion by itself.

Because of it, this code won’t compile:

auto res = Invoke([](int a, double b) {
return std::to_string(a) + " " + std::to_string(b);
);

error C2784: 'R Invoke(std::function<R(A,Args...)>)': could not deduce template argument for 'std::function<R(A,Args...)>' from 'main::<lambda_d41240fd9f3a8ae800d0c79d59eeebd7>'


Instead, to call Invoke you have to specify the cast explicitly:

auto res = Invoke(std::function<std::string(int, double)>([](int a, double b) {
return std::to_string(a) + " " + std::to_string(b);
));


Can we work around this? How can we avoid conversion and tailor Invoke to target the types of anonymous functions when they are by definition anonymous?

# Second try

We can try to let Invoke accept any type and exploit the property all anonymous functions share – all lambdas are compiled into an object (closure) with custom operator() and we can take a function pointer of it.

template<typename F>
??? Invoke(F f)
{
// Split the lambda f into the closure object and the function pointer
return DispatchFunctionPointer(f, &F::operator());
}


For now, let’s ignore the return type of f and assume f returns void.

Let’s revise the recursion with our new approach. The base case is a function pointer without any arguments:

template<typename F>
void DispatchFunctionPointer(F& f, void(F::*mf)() const)
{
return (f.*mf)(); // Call mf function pointer on the object f
}


As for function pointer taking n+1 arguments:

template<typename F, typename A, typename... Args>
void DispatchFunctionPointer(F& f, void(F::*mf)(A, Args...) const)
{
A a{}; // Prepare default value for first argument
// Use Invoke to dispatch an anonymous function with n arguments
Invoke([a, f, mf](Args... args1) {
return (f.*mf)(a, args1...);
});
}


The idea is exactly the same as before, only the syntax is a little different.

Now, how to handle the return types?

The two dispatch functions can infer their return value from the signature of the function pointer:

template<typename F, typename R>
R DispatchFunctionPointer(F& f, R(F::*mf)() const);

template<typename F, typename R, typename A, typename... Args>
R DispatchFunctionPointer(F& f, R(F::*mf)(A, Args...) const);


What about Invoke?
This is a bit more complicated since we have to infer the return type directly from F.
However, all we need is an expression returning the desired type.
We can define Invoke as follows:

template<typename F>
auto Invoke(F f) const
-> decltype(DispatchFunctionPointer(f, &F::operator()))
{
return DispatchFunctionPointer(f, &F::operator());
}


# Implications

The first simple but powerful application of this technique is overloading higher order functions (functions accepting other functions) based on the parameters of their parameters 🙂

To be more concrete, imagine you wish to define SyncronizedCall function to provide thread-safe access to some resource:

void SyncronizedCall(std::function<void(const FileResource&)> func)
{
auto& fileResource = // Get first resource
std::lock_guard<std::mutex> lock(_fileResourceMutex);
func(fileResource);
}


This works until you decide to add a second resource:

void SyncronizedCall(std::function<void(const DBConnection&)> func)
{
auto& dbResource = // Get the second resource
std::lock_guard<std::mutex> lock(_dbResourceMutex);
return func(dbResource);
}


You can’t simply overload SyncronizedCall on the type of the lambda it accepts. If you try to call:

SyncronizedCall([](const FileResource& r) {
/* Do something with the first resource r */
});
SyncronizedCall([](const DBConnection& r) {
/* Do something with the second resource r */
});


You would get a compilation error: error C2668: 'SyncronizedCall': ambiguous call to overloaded function

You now know how to overcome this limitation by defining SyncronizedCall to be generic:

template<typename F>
void SyncronizedCall(F f)
{
return SyncronizedCallInternal(f, &F::operator());
}

template<typename F>
void SyncronizedCallInternal(F& f, void(F::*mf)(const FileResource&) const)
{
auto& fileResource = // Get first resource
std::lock_guard<std::mutex> lock(_fileResourceMutex);
return (f.*mf)(fileResource);
}

template<typename F>
void SyncronizedCallInternal(F& f, void(F::*mf)(const DBConnection&) const)
{
auto& dbResource = // Get the second resource

This time the correct implementation would get called depending on the argument of f.