64

I'd like to create a class where the client can store a lambda expression like []() -> void {} as a field of the class, but I can't figure out how to do so. One answer suggested using decltype, which I tried with no success. Here is a ideone source link. The below is the source and result:

#include <cstdio>
auto voidLambda = []()->void{};

class MyClass {
public:
     decltype(voidLambda) t;
     MyClass(decltype(voidLambda) t) { 
        this->t = t;
     }
};

int main() {
   MyClass([] {
      printf("hi");
   });
}

Result:

prog.cpp: In constructor 'MyClass::MyClass(<lambda()>)':
prog.cpp:3:79: error: no matching function for call to '<lambda()>::__lambda0()'
prog.cpp:2:20: note: candidates are: <lambda()>::<lambda>(const<lambda()>&)
prog.cpp:2:20: note:                 <lambda()>::<lambda>(<lambda()>&&)
prog.cpp:3:88: error: no match for 'operator=' in '((MyClass*)this)->MyClass::t = t'
prog.cpp: In function 'int main()':
prog.cpp:5:27: error: no matching function for call to 'MyClass::MyClass(main()::<lambda()>)'
prog.cpp:3:48: note: candidates are: MyClass::MyClass(<lambda()>)
prog.cpp:3:14: note:                 MyClass::MyClass(const MyClass&)

Does anyone know how to do this?

Community
  • 1
  • 1
  • 7
    Every lambda expression creates it's own unique type. In `auto A = [](){}; auto B = [](){};` `A` and `B` are not the same type. – bames53 Feb 08 '12 at 02:05
  • 1
    Unfortunately `struct A { auto x = 0; };` is not allowed. – Johannes Schaub - litb Feb 09 '12 at 22:32
  • @bames53, I feel like your comment deserves 1000 more upvotes. Your comment is critical for understanding this adjacently-related question and answer too: [Is there a way to create a hash of a function wrapped by `std::function<>`?](https://stackoverflow.com/a/76114883/4561887) – Gabriel Staples Apr 28 '23 at 01:28

3 Answers3

83

If you want a class member to be a lambda expression, consider using the std::function<> wrapper type (from the <functional> header), which can hold any callable function. For example:

std::function<int()> myFunction = [] { return 0; }
myFunction(); // Returns 0;

This way, you don't need to know the type of the lambda expression. You can just store a std::function<> of the appropriate function type, and the template system will handle all the types for you. More generally, any callable entity of the appropriate signature can be assigned to a std::function<>, even if the the actual type of that functor is anonymous (in the case of lambdas) or really complicated.

The type inside of the std::function template should be the function type corresponding to the function you'd like to store. So, for example, to store a function that takes in two ints and returns void, you'd make a std::function<void (int, int)>. For a function that takes no parameters and returns an int, you'd use std::function<int()>. In your case, since you want a function that takes no parameters and returns void, you'd want something like this:

class MyClass { 
public:
    std::function<void()> function;
    MyClass(std::function<void()> f) : function(f) {
        // Handled in initializer list
    }
};

int main() {
    MyClass([] {
        printf("hi")
    }) mc; // Should be just fine.
}

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • Question: is `-> void` really necessary here ? I know the return clause can be omitted (and thus deduced) in a number of circumstances and this strikes me as begin an easy case (no `return`). – Matthieu M. Feb 08 '12 at 07:18
  • @MatthieuM.- I'm actually not sure! I was under the impression that you had to have it, but if I'm mistaken I'd be happy to update this answer. – templatetypedef Feb 08 '12 at 07:26
  • 1
    @MatthieuM.: If there is only one statement which is a `return`, the return type will be deduced. Otherwise, the return type is always `void`. Therefore, the `-> void` can be omitted. – kennytm Feb 08 '12 at 09:52
  • Bye the way, you could even omit the parenthesis `()` since there are no parameters. – danijar Apr 02 '13 at 10:30
  • (lambda ->) is used to explicitly define a return type such that returns from the lambda have to comply. If you omit it then any number of returns can be in place but they all have to associate to the same type or you will get a compile error. – mysticcoder May 16 '15 at 04:15
  • 2
    Why don't people include relevant includes in their answers? That would save me so much googling! – Tomáš Zato Apr 19 '17 at 18:58
  • @TomášZato I've just updated the answer to include this detail. Thanks for pointing this out - it didn't occur to me that this would be a tricky header to find. – templatetypedef Apr 19 '17 at 19:20
15

The only way I can think of to store a lambda in a class is to use a template with a helper make_ function:

#include <cstdio>
#include <utility>

template<class Lambda>
class MyClass {
    Lambda _t;
public:
    MyClass(Lambda &&t) : _t(std::forward<Lambda>(t)) {
        _t();
    }
};

template<class Lambda>
MyClass<Lambda> make_myclass(Lambda &&t) {
    return { std::forward<Lambda>(t) };
}

int main() {
    make_myclass([] {
        printf("hi");
    });
}
cubuspl42
  • 7,833
  • 4
  • 41
  • 65
2

In case of [] (empty capture) simple function pointer can be used. Declaration syntax is ReturnType (*pointer_name) (Arg1T, Arg2T); for pointer, ReturnType (&ref_name) (/*void*/); for reference (can't be null). Lambda with empty capture block is implicitly convertible to function pointer with same signature. And std::function have runtime and size (it is at least three times larger) overhead.

struct S
{
  void (*f_p)() {}; // `{}` means `= nullptr`;
};

int main()
{
    S s { [] { std::cout << "Lambda called\n"; }};

    s.f_p();

    S s2;
    if (s2.f_p) // check for null
        s.f_p();

    s2.f_p = [] { std::cout << "Lambda2 called\n"; };
    s2.f_p();

    s2.f_p = std::terminate; // you can use regular functions too

    s2.f_p();
}

Output

Lambda called
Lambda2 called
terminate called without an active exception
OwnageIsMagic
  • 1,949
  • 1
  • 16
  • 31