5

If I have a lambda which captures all automatic variables by reference ([&] {}), why can't it be converted to a function pointer? A regular function can modify variables just like a lambda that captures everything by reference can, so why is it not the same?

I guess in other words, what is the functional difference between a lambda with a & capture list and a regular function such that the lambda is not convertible to a function pointer?

Barry
  • 286,269
  • 29
  • 621
  • 977
template boy
  • 10,230
  • 8
  • 61
  • 97
  • 3
    References (really, pointers) to all those captured variables have to be stored *somewhere*. Think of "capture by reference" as "capture the address by value". – Igor Tandetnik Nov 01 '14 at 19:05
  • @IgorTandetnik: Which would be solvable, if we used a GC (possible but rare) or accepted the memory-leak. – Deduplicator Nov 01 '14 at 19:08
  • 1
    @Deduplicator: I suppose the compiler could generate an assembly thunk on the fly at run-time; I've seen this done (by Microsoft's ATL, in case you wonder). That would work equally well when capturing by value though; capturing by reference neither helps nor hurts. – Igor Tandetnik Nov 01 '14 at 19:14
  • @IgorTandetnik: Sure thing, I ignored OPs fixation on references. – Deduplicator Nov 01 '14 at 19:14
  • 1
    Is that the only thing that you wonder about with C++ lambdas? I've got a much more fundamental question: Why is it explicitly **forbidden** to write down the type of a lambda? You can assign it to an `auto` variable or pass its type as a template parameter, but that's it. There is simply no syntax to write a non-template function that takes a lambda. The types of lambdas are the only ones that can't be written down, which makes them less than second class citizen. Personally, I don't give a language feature a second thought if it is nerfed is such a fundamental way. – cmaster - reinstate monica Nov 01 '14 at 19:40
  • @cmaster: *"There is simply no syntax to write a non-template function that takes a lambda."* -- False. http://coliru.stacked-crooked.com/a/0c7e28dfef3671b7 -- Why, exactly, do you need to write the type of a lambda? I'm sure if you presented a use case for such a thing, the standards committee would be open to considering it. – Benjamin Lindley Nov 01 '14 at 19:53
  • @BenjaminLindley I looked at your example, and indeed it seems as if you had produced a typedef for the lambda type. However, the standard guarantees that *the only object of this type is the lambda that you used for the typedef*. If you added a second lambda with this declaration `auto lambda2 = [](){ std::cout << "hello\n"; };`, you would not be able to pass `lambda2` to `func()`, because the type of a lambda is unique to the place of creation. The use case, of course, is to write non-template functions that can accept a lambda as an argument. – cmaster - reinstate monica Nov 01 '14 at 20:05
  • @cmaster: And what would be the use of doing that? "Use case" implies usefulness, not just a bullet point of a thing that the feature would allow. – Benjamin Lindley Nov 01 '14 at 20:16
  • @BenjaminLindley Oh, a non-template function is much more useful than a templated one. It does not need to be defined in a header. It can be compiled into a library. It can be supplied in a module that is loaded at runtime. It can be compiled on its own. You don't have to recompile everything that directly or indirectly uses it if you change its body. It's really much more flexible than the templated one, and it wastes much less time for compilation, boosting your productivity. Really, non-template should always be the default, and templates only be used where they are required. – cmaster - reinstate monica Nov 01 '14 at 20:54
  • @cmaster Last I checked, classes and structs were not deprecated. :-) And you are really going to hate C++14 lambdas, which can be generic (the equivalent of a templated function call operator). With non-capturing non-generic lambdas, you can just easily convert it (via unary '+' or unary '*') to a function pointer of the correct signature, so I don't see a need for that to have a spellable type. If it is capturing, you have to define it where the capture takes place, and cannot instantiate it anywhere else (other than by copy or [C++14] move), so I also don't see why a spellable name helps – Nevin Nov 01 '14 at 21:12
  • @Nevin I don't hate templates, and I have no opinion on generic lambdas. The thing that really alerts me is that lambdas break orthogonality: Since ANSI C I have been able to write down the type of each and every value in C/C++. That alone is enough reason for demanding the same thing for all new values that are introduced into the language. Lambdas break this brutally. This is a very dangerous road that C++ has taken. And it will eventually render the language unmanageable if it continues down this road. – cmaster - reinstate monica Nov 01 '14 at 21:35
  • @cmaster You can use std::function with lambdas, so you can write the implementation in a sperate file. –  Nov 02 '14 at 12:40
  • @manni66 Again, my point is not about how lambdas can be used in general. My point is about the simple fact that you can't write down its type. I really don't care if a workaround exists. The fact that a workaround is needed already shows that the concept of lambdas in C++11 is fundamentally broken. It's even unredeemably broken since you have this ghastly rule in the language now, that in `auto lambda1 = [](){}; auto lambda2 = [](){};` the two variables do not have the same type. You can't even add a syntax to write down the type of a lambda without removing this rule. – cmaster - reinstate monica Nov 02 '14 at 13:24

2 Answers2

10

So let's take the example of a trivial lambda:

Object o;
auto foo = [&]{ return o; };

What does the type of foo look like? It might look something like this:

struct __unique_unspecified_blah
{
    operator()() const {
        return o;
    }

    Object& o;
};

Can you create a function pointer to that operator()? No, you can't. That function needs some extra information that comes from its object. This is the same reason that you can't convert a typical class method to a raw function pointer (without the extra first argument where this goes). Supposing you did create some this pointer - how would it know where to get o from?

The "reference" part of the question is not relevant - if your lambda captures anything, then its operator() will need to reference some sort of storage in the object. If it needs storage, it can't convert to a raw function pointer.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • "This is the same reason that you can't convert a typical class method to a raw function pointer." Nope, that's a calling-convention thing. Now if you meant to a raw function-pointer needing fewer arguments (like not needing `this`)... – Deduplicator Nov 01 '14 at 19:11
  • @Deduplicator: I took that to be exactly what he meant given the rest of the explanation... – Ed S. Nov 01 '14 at 19:15
  • @EdS.: It's just that G++ is happy to convert member-function-pointers to regular function-pointers for you, and they work without a hitch. – Deduplicator Nov 01 '14 at 19:17
  • @Deduplicator: non-virtual only, I presume? I don't quite see how it could work in the general case. – Igor Tandetnik Nov 01 '14 at 19:18
  • @Deduplicator how can you convert a member-function-pointer to a regular-function-pointer? I thought they'd be different types (like `void(*)()` vs `void(Obj::*)()`) – Barry Nov 01 '14 at 19:21
  • @IgorTandetnik: Yes, if it's a `virtual` function, that conversion needs a specific most-derived-class / object to resolve the virtual dispatch against. – Deduplicator Nov 01 '14 at 19:21
  • @Barry: Your example is a static member-function, as non-static member-functions have a `this`-argument first. And it's a conforming extension. – Deduplicator Nov 01 '14 at 19:22
  • @Deduplicator wow, I did not know you could do that. Is that really standard? – Barry Nov 01 '14 at 19:28
  • Decided to move this conversation fragment into a [new question](http://stackoverflow.com/questions/26692378/passing-this-as-first-argument-for-a-method-is-it-standard). – Barry Nov 01 '14 at 19:37
  • But why could the lambdas without capture could be converted to function poniter? – choxsword May 22 '18 at 08:27
3

I guess in other words, what is the functional difference between a lambda with a & capture list and a regular function such that the lambda is not convertible to a function pointer?

References, though they aren't objects, need to be stored somewhere. A regular function cannot access local variables of another function; Only references (e.g. as parameters) that could refer to local variables. A Lambda with a & as the capture-default can, because every variable needed can be captured.
In other words: A regular function doesn't have state. A closure object with captured variables does have state. So a closure object cannot be reduced to a regular function, because the state would be lost.

Columbo
  • 60,038
  • 8
  • 155
  • 203