75

Can lambdas be defined as data members?

For example, would it be possible to rewrite the code sample below using a lambda instead of a function object?

struct Foo {
    std::function<void()> bar;
};

The reason I wonder is because the following lambdas can be passed as arguments:

template<typename Lambda>
void call_lambda(Lambda lambda) // what is the exact type here?
{ 
    lambda();
}

int test_foo() {
    call_lambda([] { std::cout << "lambda calling" << std::endl; });
}

I've figured that if a lambda can be passed as a function argument, then maybe they can also be stored as a data member.

After more tinkering I found that this works (but it's kind of pointless):

auto say_hello = [] { std::cout << "Hello"; };

struct Foo {
    using Bar = decltype(say_hello);
    Bar bar;
    Foo() : bar(say_hello) {}
};
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
StackedCrooked
  • 34,653
  • 44
  • 154
  • 278
  • 2
    The type name of a lambda is unknown to you, and generated by the compiler (one per lambda function). The `std::function` template was introduced precisely as a type eraser for this kind of situations. – Alexandre C. Jul 06 '11 at 19:39
  • 1
    Did you try it? why are you asking us to do it for you? if it errored then come here and post the error too. – Daniel Jul 06 '11 at 19:39
  • 1
    Lambdas **are** function objects! Yes, you can make `bar` a lambda. – Kerrek SB Jul 06 '11 at 19:39
  • @Dani: If it will be called anything it is C++11. No matter what year it becomes a standard (the ISO expects publication by 2012-02-28 btw). For comparison Fortran 2008 was published in 2010. – R. Martinho Fernandes Jul 06 '11 at 19:45
  • 1
    Some lambdas can also become function pointers. – R. Martinho Fernandes Jul 06 '11 at 19:47
  • @Xeo, they once said it will be done in 200x, which passed. their latest assessment was mid-2011, which also passed. My personal guess is mid-late 2012 or early 2013. – Daniel Jul 06 '11 at 19:48
  • @Martinho: Capture-less lambdas are implicitly convertible to function pointers. – Xeo Jul 06 '11 at 19:48
  • 2
    @Dani: Ehm, the standard group itself finished everything, made a FDIS (Final Draft International Standard) [in March](http://stackoverflow.com/questions/5436139/when-will-c0x-be-finished). All that's left is a sign from ISO. – Xeo Jul 06 '11 at 19:49
  • @Xeo: note that the implicit conversion of capture-less lambdas to function pointers was fairly recently added to the spec, so some implementations (VC2010) don't implement it yet. – Nicol Bolas Jul 06 '11 at 20:15
  • @Nicol: I am aware of that, and it's too bad VC10 (or rather, the proposal) didn't make it on time. :/ – Xeo Jul 06 '11 at 20:18

6 Answers6

58

A lambda just makes a function object, so, yes, you can initialize a function member with a lambda. Here is an example:

#include <functional>
#include <cmath>

struct Example {

  Example() {
    lambda = [](double x) { return int(std::round(x)); };
  };

  std::function<int(double)> lambda;

};
wjl
  • 7,519
  • 2
  • 32
  • 41
  • 6
    Why not use the initializer list?? – Kerrek SB Jul 06 '11 at 21:25
  • Do modern compilers really not optimize that case? – Nevir Dec 26 '13 at 22:52
  • 8
    I didn't put the function in the initializer list in my example on purpose, because I figured that chances are if you're really doing something like the above in a real situation, your lambda would actually be doing captures or something more complex where it wouldn't make sense in the initializer list. But of course you could make this `Example() : lambda([](double x) { return int(std::round(x)); }) {}` if you want. =) – wjl Dec 27 '13 at 04:04
  • 1
    Because lambda expressions yield callable objects, closures can be stored in std::function objects. Lambdas don't 'just make' function objects. – sungiant Dec 12 '16 at 15:24
  • 1
    @sungiant Well, if you want to argue definitions, yes lambda's do 'just make' function objects -- but you are correct that they do not 'just make' `std::function` objects. Every lambda -- even those defined exactly the same way -- is a function object of a distinct class. Fortunately, these can be used transparently and polymorphically with type erasure wrappers like `std::function`. =) – wjl Dec 13 '16 at 20:17
  • Lambdas do not make `std::function<...>` objects. That said, they are implicitly convertible to one. There are several important distinctions. First, a lambda does not heap allocate, but `std::function` does. Lambdas without any captures are actually normal function types (e.g. `void (&)(int, char)`). – Thomas Eding Dec 21 '20 at 14:30
30

Templates make it possible without type erasure, but that's it:

template<typename T>
struct foo {
    T t;
};

template<typename T>
foo<typename std::decay<T>::type>
make_foo(T&& t)
{
    return { std::forward<T>(t) };
}

// ...
auto f = make_foo([] { return 42; });

Repeating the arguments that everyone has already exposed: []{} is not a type, so you can't use it as e.g. a template parameter like you're trying. Using decltype is also iffy because every instance of a lambda expression is a notation for a separate closure object with a unique type. (e.g. the type of f above is not foo<decltype([] { return 42; })>.)

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • except that you cannot have a member variable of function type without knowing its type, ie. without type erasure. – Alexandre C. Jul 06 '11 at 22:15
  • @Alexandre This is not about `std::function`, assuming you meant that. If you didn't, I don't understand. – Luc Danton Jul 06 '11 at 22:26
  • not necessarily. I just mean that you cannot declare member variables of type `foo` without knowing `T`. To do this, you have to erase `T` somehow. This limits what you can do with your approach (which is good by the way, I learnt about `std::decay` and it reminded me to use initializer lists when I can). – Alexandre C. Jul 06 '11 at 22:29
  • 1
    @Alexandre That's what the 'without type erasure' means at the top. – Luc Danton Jul 06 '11 at 22:33
  • 2
    In my case, it's important that all functions in the array have the same argument and return types, so vanilla std::function works just fine. I am still wondering about the overhead – Miles Jan 24 '14 at 21:24
21

A bit late, but I have not seen this answer anywhere here. If the lambda has no capture arguments, then it can be implicitly cast to a pointer to a function with the same arguments and return types.

For example, the following program compiles fine and does what you would expect:

struct a {
    int (*func)(int, int);
};

int main()
{
    a var;
    var.func = [](int a, int b) { return a+b; };
}

Of course, one of the main advantages of lambdas is the capture clause, and once you add that, then that trick will simply not work. Use std::function or a template, as answered above.

Shachar Shemesh
  • 8,193
  • 6
  • 25
  • 57
10
#include <functional>

struct Foo {
    std::function<void()> bar;
};

void hello(const std::string & name) {
    std::cout << "Hello " << name << "!" << std::endl;
}

int test_foo() {
    Foo f;
    f.bar = std::bind(hello, "John");

    // Alternatively: 
    f.bar = []() { hello("John"); };
    f.bar();
}
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
4

As long as lambda is constant (without closures), you can do something like this:

#include <iostream>

template<auto function>
struct Foo
{
    decltype(function) bar = function;
};

void call_lambda(auto&& lambda)
{
    lambda();
}

int main()
{
    Foo<[](){ std::cout << "Hello"; }> foo;
    foo.bar();
    call_lambda(foo.bar);
}

https://godbolt.org/z/W5K1rexv3

Or we can apply deduction guides to make it work for all lambdas:

#include <iostream>

template<typename T>
struct Foo 
{
    T bar;
};

template<typename T>
Foo(T) -> Foo<std::decay_t<T>>;

void call_lambda(auto&& lambda)
{
    lambda();
}

int main()
{
    std::string hello = "Hello";
    Foo foo([&](){ std::cout << hello; });
    foo.bar();
    call_lambda(foo.bar);
}

https://godbolt.org/z/cenrzTbz4

Konard
  • 2,298
  • 28
  • 21
3

"if a lambda can be passed as a function argument then maybe also as a member variable"

The first is a yes, you can use template argument deduction or "auto" to do so. The second is probably no, since you need to know the type at declaration point and neither of the previous two tricks can be used for that.

One that may work, but for which I don't know whether it will, is using decltype.

dascandy
  • 7,184
  • 1
  • 29
  • 50
  • `decltype` wouldn't work because every *occurrence* of a lambda expression is of a different type and they're not convertible between them. But apparently you can't even use lambdas in decltype. – R. Martinho Fernandes Jul 06 '11 at 20:02