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.