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 argument 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 StackOverflow 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:
template<typename T> class Lambda {};
Second, specialize the template using the simple function signature syntax:
template<typename Out, typename... In> class Lambda<Out(In...)> {};
Now, if you use Lambda like this:
Lambda<int(bool a, int *b)>
"Out" will be "int
" and "In..." will be "bool, 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.
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:
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.
#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
Lambda<int(int)> storage = [](int a) { return a + 1; };
int z = 18000000;
storage = [z](int a) { return a + 1 + z; };
int y = storage(4);
History
- Jan 17, 2012: Fixed the Simple Function Signature Templates section example explanation.
- Jan 13, 2012: Added void return value support.
- Jan 11, 2012: Initial submission.