8

I've encountered a situation that challenges my nascent understanding of C++ lambdas, and I've distilled it down to the following:

#include <iostream>

void test()
{
    int (*func)();

    func =
        []()->int {
            std::cerr << "func()" << std::endl;
            return 0;
        };

    int i = 0;
    func =
        [i]()->int {
            std::cerr << "func(): i= " << i << std::endl;
            return 0;
        };
}

In the first case, I'm assigning a very simple lambda to a function pointer, and it seems to work as I would expect. What I'm trying to do in the second case is to provide the lambda with access to the value of i. My understanding of [i]()->int {code} is that it defines a nameless function that takes no arguments, returns an int and, through the magic of the C++11 unicorns, knows the current value of i. I would expect that this lambda should be callable as int(*)().

test.cpp: In function ‘void test()’:
test.cpp:14:7: error: cannot convert ‘test()::__lambda1’ to ‘int (*)()’ in assignment
  func =
       ^

It would seem that gcc 4.8.1 and 4.8.2 disagree with my assessment (4.4.1 refused to even discuss the matter).

This seems to suggest that the type of that second lambda is not assignment-compatible with the function pointer. I don't understand why that would be the case, given that that expression should be callable as "int(*)()".

Where has my understanding (or the unicorns) failed me?

John Auld
  • 476
  • 2
  • 12
  • 12
    Lambdas only convert to function pointers if they don't capture anything. How would the function pointer hold `i`? – chris Aug 18 '14 at 01:45
  • [See stateless lambdas.](https://stackoverflow.com/questions/19961873/test-if-a-lambda-is-stateless) –  Aug 18 '14 at 01:46
  • Particularly this [answer](http://stackoverflow.com/a/19962132/3920237) which quotes § 5.1.2/6. –  Aug 18 '14 at 01:53
  • @chris - My thinking was basically that the lambda expression, in effect, creates a class with member variables for the captures and an operator()() (the "unicorns" I glossed over in my question). On further consideration, it's clear to me why this can't be hidden behind a simple function pointer. – John Auld Aug 18 '14 at 04:33

3 Answers3

9

The C++ Standard, section § 5.1.2 / 6 , defines how a lambda can convert to a (possibly template) function pointer.

Particulary :

The closure type for a non-generic lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function with C ++ language linkage (7.5) having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator

Since your lambda has a capture, it can't convert to a function pointer.


Note:

  • Beware that captures can be implicit or default with a lamdba.

i.e. the following is not valid either:

int i = 0;
func =
    [&]()->int {
        std::cout << "func(): i= " << i << std::endl;
        return 0;
    }

Live demo

quantdev
  • 23,517
  • 5
  • 55
  • 88
2

Being invokable with signature int() does not mean it can be converted to a int(*)(). Anything with an overloaded int operator()() is invokable that way.

Lambdas create pretty bog standard classes with such an overload, then wrap them in some syntactic sugar. (not true, but true enough)1

Stateless lambdas (ones that capture nothing) come with a bonus operator int(*)() overload (or operator Signature* in general), but that is just a bonus, not core to a lambda's being.

A function pointer is, in practice, the address that execution jumps to when you invoke the function. There is no room for a data pointer as well (to store the state of your captured variables). In theory you could allocate space for the data alongside or within the execution code, but many systems block marking writable pages as executable as well for security reasons, which makes that system impractical. There have been people who have proposed that kind of extension.


1 As with many things in the C++ standard, things are specified in terms of behavior not implementation. Most features of lambdas can be duplicated by implementing bog standard classes "auto-written" for you, but even then the compiler doesn't have to do it. Some lambda implementations (like stack frame capture based [&] lambdas) cannot be implemented within the C++ language.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

You cannot convert a lambda to a function pointer if it carries state. If you think twice, you will see that an ordinary function pointer cannot carry state from its environment.

So your second lambda is not convertible, but the first it is, because the first lambda amounts to a function pointer.

Germán Diago
  • 7,473
  • 1
  • 36
  • 59