cowculus
cowculus

Reputation: 73

C++ save lambda-functions as member-variables without function pointers for optimization

I would like to write in C++ a class that saves lambda functions as member variables. It would be great to do it as efficient as possible. For example, I read this thread Why can lambdas be better optimized by the compiler than plain functions? and therefore I want to avoid using function pointers.

So far my best solution is the following:

template<typename F>
class LambdaClass {
  private:
    F lambdaFunc;
  public:
    LambdaClass(F &_lambdaFunc): lambdaFunc(_lambdaFunc) {}
};

I would use this class as follows:

auto lambdaFunc = [](int _a) -> int { return _a; };
LambdaClass<decltype(lambdaFunc)> lambdaClassObject<decltype(lambdaFunc)>(lambdaFunc);

In my opinion this doesn't look like fun using it. So I am interested in first if this code is efficient in the sense that the compiler could inline the calls of the saved member lambda function and second how one could write this code more beautiful?

Edit: I am using C++ 11.

Upvotes: 7

Views: 2600

Answers (4)

lubgr
lubgr

Reputation: 38295

When you're able to use C++17, you can use template argument deduction for classes. The instantiation then looks like this:

auto lambdaFunc = [](int _a) -> int { return _a; };
LambdaClass lambdaClassObject(lambdaFunc);

which looks like more fun using it. This use case does not impose any restrictions when it comes to inlining the lamdba invocation.

Note that it might be desirable to pass temporaries to you constructor. In this case, use an rvalue reference that is explicitly turned into a forwarding reference by the necessary deduction guide:

template<typename F> class LambdaClass {
    private:
        F lambdaFunc;
    public:
        LambdaClass(F&& _lambdaFunc) : lambdaFunc(std::forward<F>(_lambdaFunc)) {}
};

// Deduction guide, makes the ctor accept lvalue and rvalue arguments:
template<class F> LambdaClass(F&&) -> LambdaClass<F>;

You can now instantiate a LambdaClass object with the lvalue-lamdba above or by

LambdaClass lambdaClassObject([](){ /* Do stuff. */ });

As pointed out by @Holt, @aschepler and @Caleth in the comments, the type deduction results in F& when lvalues are passed to the constructor, which is unlikely to be the desired instantiation. I couldn't get std::remove_reference_t or std::decay_t to do the work in the deduction guide as suggested by @Caleth, but found a solution that requires no deduction guide, but instead an overloaded constructor:

template<typename F> class LambdaClass {
    private:
        F lambdaFunc;
    public:
        LambdaClass(F&& _lambdaFunc) : lambdaFunc(std::forward<F>(_lambdaFunc)) {}
        LambdaClass(F& _lambdaFunc) : lambdaFunc(_lambdaFunc) {}
};

// Note: Deduction guide is gone. The first ctor accepts only an rvalue reference.

This allows construction with lvalue or rvalue arguments with the "intuitive" type being deduced by the compiler.

Upvotes: 5

aschepler
aschepler

Reputation: 72431

In your example

LambdaClass<decltype(lambdaFunc)> lambdaClassObject<decltype(lambdaFunc)>(lambdaFunc);

the second template argument list is incorrect syntax. This needs to be just

LambdaClass<decltype(lambdaFunc)> lambdaClassObject(lambdaFunc);

So I am interested in first if this code is efficient in the sense that the compiler could inline the calls of the saved member lambda function

Yes, this class can be used in ways that will allow optimizations pretty much just like using a lambda directly. The template argument is the exact type of the lambda expression, and template substitution happens at compile time, usually giving results just like you would get by writing out code without using templates.

How one could write this code more beautiful?

@lubgr's answer already mentions the C++17 "class template deduction" and "deduction guide" features. Prior to C++17, the usual trick to avoid needing to specify class template arguments is a helper "make function":

template <typename F>
auto makeLambdaClass(F&& func) ->
    LambdaClass<typename std::decay<F>::type>
{ return { std::forward<F>(func); } }

Now you can do

auto lambdaFunc = [](int _a) -> int { return _a; };
auto lambdaClassObject = makeLambdaClass(lambdaFunc);

But to go a step further and make

auto lambdaClassObject = makeLambdaClass( [](int _a) -> int { return _a; } );

also work, you'll also need to make sure the class has a constructor that accepts an rvalue, not just a non-const lvalue:

template<typename F>
class LambdaClass {
  private:
    F lambdaFunc;
  public:
    LambdaClass(const F &lambdaFunc_): lambdaFunc(lambdaFunc_) {}
    LambdaClass(F &&lambdaFunc_) : lambdaFunc(std::move(lambdaFunc_)) {}
};

By the way, this class will work just as well with a callable class that is not a lambda's closure type, since a lambda is just a more convenient way of defining a class with an operator():

class UniqueUIntGenerator
{
public:
    unsigned int operator()() const noexcept
    { return num++; }
private:
    static unsigned int num;
};
unsigned int UniqueIntGenerator::num = 0;

LambdaClass<UniqueIntGenerator> gen{UniqueIntGenerator{}};

Upvotes: 10

Max Langhof
Max Langhof

Reputation: 23701

In C++ 11, only functions can deduce template arguments automatically. So, just write a make-function:

template<typename F>
LambdaClass<F> makeLambdaClass(F&& f)
{
  return LambdaClass<F>(std::forward<F>(f));
}

This lets you omit the template argument in the usage:

auto lambdaFunc = [](int _a) -> int { return _a; };
auto lambdaClassObject = makeLambdaClass(lambdaFunc);

You should however be aware that this only passes the problem upwards: If somebody else wants to use LambdaClass as a member, they either have do the same class templating shenanigans or roll their own type erasure. Maybe this is not a problem for you, but you should be aware of it.

Upvotes: 3

YSC
YSC

Reputation: 40110

In C++11, the simplest one can achieve is to delegue type inference to a template-function:

template<class F>
auto make_lambda_class(F&& f) -> LambdaClass<decltype(f)>
{ return f; }

You would call it like:

auto lambdaFunc = [](int _a) -> int { return _a; };
auto lambdaClassObject = make_lambda_class(lambdaFunc);

But, sadly, you cannot pass an rvalue to make_lambda_class. You'd need C++17 to do that, as lubgr shown.

Upvotes: 2

Related Questions