Introduction
This code project demonstrates how to write a lambda storage class that functions similarly to std::function, only for lambda functions. That is, you specify the return type and argment types of the function, and the class will store any lambda that matches, including those that capture variables. For those of you who doubt the usefulness of such a utility, what if your significant other tells you to stop using libc++ and there's no helping it? This is exactly for those sort of situations.
It also demonstrates that std::function can be implemented without relying on compiler hooks and whatnot.
Background
A number of C++11 (I think) features are required to do this. To get the simple function signature (return type and parameter types), the utility uses a unique method of variadic template specialization. The utility stores the lambda in a void pointer, and it uses a number of templatized lambda functions to retain lambda type information where necessary. I use nullptr to be especially prissy.
When we're done, if you really want to get crazy, you can even use move constructors, temporary value references, and other optimizations that are, quite frankly, completely beyond the scope of this article (i.e. my ability).
Simple Function Signature Templates
Like with std::function, it is possible to pass a function return type and arguments as template parameters. I found this technique referenced by a stack overflow post (here, which references the standard:http://stackoverflow.com/a/3535871 ). Basically, there are two steps that both seem to be necessary.
First, create a templatized class with one parameter:
Collapse| Copy Code
template<typename T> class Lambda {};
Second, specialize the template using the simple function signature syntax:
Collapse| Copy Code
template<typename Out, typename... In> class Lambda<Out(In...)> {};
Now, if you use Lambda like this:
Collapse| Copy Code
Lambda<int(bool a, int *b)>
"Out" will be "bool" and "In" will be "int *".
Storing and Managing Lambda Function Pointers
The utility cannot hold the lambda function in a function pointer because this would prevent it from storing lambdas with captured variables. The utility also cannot use auto, since even lambdas with exactly identical specifications are separate classes and are therefore not assignable. Therefore, the utility stores the lambda as a void pointer. Because the source lambda may go out of scope before the utility class is destroyed, the utility class cannot store a pointer to the original lambda but must duplicate and store its own pointer.
Collapse| Copy Code
void *lambda;
template<typename LambdaType> Lambda<Out(In...)> &operator =(LambdaType const &lambda)
{
this->lambda = new LambdaType(lambda);
return *this;
}
(We know the copy constructor for the lambda expression exists because "auto a = [](){}; auto b = a;" is valid. QED And Baxter 11 Jan 2012)
With this alone, the utility loses all of the type information and is unable to delete or call the lambda function any more. To get around this, it can use template-generated lambda functions to do the type-specific operations:
Collapse| Copy Code
void *lambda;
Out (*executeLambda)(void *, In...);
void (*deleteLambda)(void *);
template<typename LambdaType> Lambda<Out(In...)> &operator =(LambdaType const &lambda)
{
if (this->lambda != nullptr) deleteLambda(this->lambda);
this->lambda = new LambdaType(lambda);
executeLambda = [](void *lambda, In... arguments) -> Out
{
return ((LambdaType *)lambda)->operator()(arguments...);
};
deleteLambda = [](void *lambda)
{
delete (LambdaType *)lambda;
};
return *this;
}
Out operator()(In ... in)
{
assert(lambda != nullptr);
return executeLambda(lambda, in...);
}
Note that the utility can use normal function pointers to store the conversion lambdas since they are non-capturing. Now, if we just add convenience constructors, copy constructors, a destructor, and any other finishing touches we'll be done.
Completed Code
Before showing the final utility, I just want to mention that the operator() above will break if the return type of the stored lambda is void. To get around this, I separated the execution code into a separate class with different specializations for void and non-void return types.
Collapse| Copy Code
#include <cassert>
template<typename T> class LambdaExecutor {};
template <typename Out, typename... In> class LambdaExecutor<Out(In...)> {
public:
Out operator()(In ... in)
{
assert(lambda != nullptr);
return executeLambda(lambda, in...);
}
protected:
LambdaExecutor(void *&lambda) : lambda(lambda) {}
~LambdaExecutor() {}
template <typename T> void generateExecutor(T const &lambda)
{
executeLambda = [](void *lambda, In... arguments) -> Out
{
return ((T *)lambda)->operator()(arguments...);
};
}
void receiveExecutor(LambdaExecutor<Out(In...)> const &other)
{
executeLambda = other.executeLambda;
}
private:
void *λ
Out (*executeLambda)(void *, In...);
};
template <typename... In> class LambdaExecutor<void(In...)> {
public:
void operator()(In ... in)
{
assert(lambda != nullptr);
executeLambda(lambda, in...);
}
protected:
LambdaExecutor(void *&lambda) : lambda(lambda) {}
~LambdaExecutor() {}
template <typename T> void generateExecutor(T const &lambda)
{
executeLambda = [](void *lambda, In... arguments)
{
return ((T *)lambda)->operator()(arguments...);
};
}
void receiveExecutor(LambdaExecutor<void(In...)> const &other)
{
executeLambda = other.executeLambda;
}
private:
void *λ
void (*executeLambda)(void *, In...);
};
template <typename T> class Lambda {};
template <typename Out, typename ...In> class Lambda<Out(In...)> :
public LambdaExecutor<Out(In...)> {
public:
Lambda() : LambdaExecutor<Out(In...)>(lambda),
lambda(nullptr), deleteLambda(nullptr), copyLambda(nullptr)
{
}
Lambda(Lambda<Out(In...)> const &other) : LambdaExecutor<Out(In...)>(lambda),
lambda(other.copyLambda ? other.copyLambda(other.lambda) : nullptr),
deleteLambda(other.deleteLambda), copyLambda(other.copyLambda)
{
receiveExecutor(other);
}
template<typename T>
Lambda(T const &lambda) : LambdaExecutor<Out(In...)>(this->lambda), lambda(nullptr)
{
copy(lambda);
}
~Lambda()
{
if (deleteLambda != nullptr) deleteLambda(lambda);
}
Lambda<Out(In...)> &operator =(Lambda<Out(In...)> const &other)
{
this->lambda = other.copyLambda ? other.copyLambda(other.lambda) : nullptr;
receiveExecutor(other);
this->deleteLambda = other.deleteLambda;
this->copyLambda = other.copyLambda;
return *this;
}
template<typename T> Lambda<Out(In...)> &operator =(T const &lambda)
{
copy(lambda);
return *this;
}
operator bool()
{
return lambda != nullptr;
}
private:
template<typename T>
void copy(T const &lambda)
{
if (this->lambda != nullptr) deleteLambda(this->lambda);
this->lambda = new T(lambda);
generateExecutor(lambda);
deleteLambda = [](void *lambda)
{
delete (T *)lambda;
};
copyLambda = [](void *lambda) -> void *
{
return lambda ? new T(*(T *)lambda) : nullptr;
};
}
void *lambda;
void (*deleteLambda)(void *);
void *(*copyLambda)(void *);
};
And Bob's your uncle. Peace out, homeboys!
Usage
Collapse| Copy Code
Lambda<int(int)> storage = [](int a) { return a + 1; };
int z = 18000000;
storage = [z](int a) { return a + 1 + z; };
int y = storage(4);