C++11 lambda to function pointer or std::function [Wednesday, 2013-01-23]
I recently needed to store callbacks in a map, so I needed a proper way to get a common castable object from a lambda, so either a function pointer or an std::function.
Now, lambdas in C++ are instances of an anonymous class specifically created for each lambda object, this means that two lambdas with the same code are actually different objects.
This also means that you can’t pass lambdas around without using a template because there’s no lambda type.
Non capturing lambdas (the ones with nothing inside the []
) can be converted
to their function pointer by casting them with the corresponding signature,
while capturing ones can be wrapped in std::function.
This means you can do:
auto lambda = [](int a, float b) {
std::cout << "a: " << a << std::endl;
std::cout << "b: " << b << std::endl;
};
auto function = static_cast<void (*)(int, float)>(lambda);
function(23, 5.4);
auto function2 = static_cast<std::function<void(int, float)>>(lambda);
function2(23, 5.4);
And it will work properly, function
being a raw pointer to the function and
function2
being an std::function object.
This also means that we can cast function
to a more general pointer like
(void (*)())
and function2
to void*
and store it in a struct and it will
be subsequentially castable to a working function, knowing the types to call it
with.
The issue arises when you don’t know the signature of the lambda, you can only cast knowing the signature, and I didn’t want to have to write the parameters twice, so here comes the solution to get the proper signature out of a lambda:
template <typename Function>
struct function_traits
: public function_traits<decltype(&Function::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
typedef ReturnType (*pointer)(Args...);
typedef std::function<ReturnType(Args...)> function;
};
template <typename Function>
typename function_traits<Function>::pointer
to_function_pointer (Function& lambda)
{
return static_cast<typename function_traits<Function>::pointer>(lambda);
}
template <typename Function>
typename function_traits<Function>::function
to_function (Function& lambda)
{
return static_cast<typename function_traits<Function>::function>(lambda);
}
With this, we can now get the function pointer out of the lambda or wrap it in an std::function, and store it.
To call that function we can then do something like this:
template <typename... Args>
void
call (void (*function)(), Args... args)
{
static_cast<void (*)(Args...)>(function)(args...);
}
template <typename... Args>
void
call (void* function, Args... args)
{
(*static_cast<std::function<void(Args...)>*>(function))(args...);
}
Bam, we have callbacks that are called and used normally, instead of reverting
to cstdarg
and va_list
.
Type safe callbacks (full example, supports capturing lambdas)
So far calls are not type safe, as in they can be casted to handle whatever arguments even if the lambda didn’t have those argument types or argument number, this can lead to very weird bugs and this isn’t a very nice solution, this is C++ not C.
We can use typeid
to ensure the arguments are of the appropriate type and
number.
#include <iostream>
#include <functional>
#include <stdexcept>
#include <typeinfo>
#include <string>
#include <map>
template <typename Function>
struct function_traits
: public function_traits<decltype(&Function::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
typedef ReturnType (*pointer)(Args...);
typedef std::function<ReturnType(Args...)> function;
};
template <typename Function>
typename function_traits<Function>::pointer
to_function_pointer (Function& lambda)
{
return static_cast<typename function_traits<Function>::pointer>(lambda);
}
template <typename Function>
typename function_traits<Function>::function
to_function (Function& lambda)
{
return static_cast<typename function_traits<Function>::function>(lambda);
}
class callbacks final
{
struct callback final
{
void* function;
const std::type_info* signature;
};
public:
callbacks (void)
{
}
~callbacks (void)
{
for (auto entry : _callbacks) {
delete static_cast<std::function<void()>*>(entry.second.function);
}
}
template <typename Function>
void
add (std::string name, Function lambda)
{
if (_callbacks.find(name) != _callbacks.end()) {
throw std::invalid_argument("the callback already exists");
}
auto function = new decltype(to_function(lambda))(to_function(lambda));
_callbacks[name].function = static_cast<void*>(function);
_callbacks[name].signature = &typeid(function);
}
void
remove (std::string name)
{
if (_callbacks.find(name) == _callbacks.end()) {
return;
}
delete static_cast<std::function<void()>*>(_callbacks[name].function);
}
template <typename ...Args>
void
call (std::string name, Args... args)
{
auto callback = _callbacks.at(name);
auto function = static_cast<std::function<void(Args...)>*>(
callback.function);
if (typeid(function) != *(callback.signature)) {
throw std::bad_typeid();
}
(*function)(args...);
}
private:
std::map<std::string, callback> _callbacks;
};
int
main (int argc, char* argv[])
{
callbacks cb;
cb.add("lol", [](int a, float b) {
std::cout << "a: " << a << std::endl;
std::cout << "b: " << b << std::endl;
});
cb.call("lol", 23, 5.4f);
cb.call("lol", 23, 43);
return 0;
}
Explanation of the magic
The first function_traits
inherits from the second, it just simplifies the
definition of the function type.
Every lambda objects has an operator()
method which is used to call the
lambda, this means that its definition contains both the return type and the
parameters types.
The decltype
of a method, is composed of the class, the return type and the
list of arguments types.
The decltype is used to define a typedef for a function pointer with the proper
return type and arguments types inferred from the decltype
of the method.
to_function_pointer
and to_function
do nothing else but use the typedef
defined in the function_traits
template to then cast the lambda to its
function pointer.
Bam, magic.