Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++11

C++11 Lambda Storage Without libc++

4.78/5 (11 votes)
16 Jan 2012CPOL3 min read 30.3K  
In the case that you need to store and pass lambdas around but can't use std::function, here's an alternative.

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:

C++
template<typename T> class Lambda {};

Second, specialize the template using the simple function signature syntax:

C++
template<typename Out, typename... In> class Lambda<Out(In...)> {};

Now, if you use Lambda like this:

C++
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.

C++
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:

C++
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.

C++
#include <cassert>

// LambdaExecutor is an internal class that adds the ability to execute to
// Lambdas. This functionality is separated because it is the only thing
// that needed to be specialized (by return type).

// generateExecutor or receiveExecutor must be called after constructing,
// before use
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 *&lambda;
    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 *&lambda;
    void (*executeLambda)(void *, In...);
};

// Lambda contains most of the lambda management code and can be used
// directly in external code.
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 should set all variables
      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

C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)