Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this C++11 lambda not behave as I expect?

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?

like image 373
John Auld Avatar asked Aug 18 '14 01:08

John Auld


Video Answer


2 Answers

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

like image 186
quantdev Avatar answered Sep 19 '22 06:09

quantdev


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.

like image 34
Yakk - Adam Nevraumont Avatar answered Sep 19 '22 06:09

Yakk - Adam Nevraumont