Search code examples
c++lambdacapturetype-deduction

Type deduction for variable declared in lambda's capture


In this snippet I am declaring a variable i inside the lambda's capture.

int main()
{
    vector<int> vec;

    // Pushing 0-99 numbers to vector
    std::generate_n(std::back_inserter(vec), 100, [i = 0]() mutable { return i++; });

    return 0;
}

As one can see, there is no type stated anywhere for i.

As far as I know, I can write the equivalent functionality in this way:

int main()
{
    vector<int> vec;

    // Pushing 0-99 numbers to vector
    std::generate_n(std::back_inserter(vec), 100, [](){
             static int i = 0; 
             return i++; });

    return 0;
}

How does the compiler know the type of i in the first example? Is it deduced from the operation I perform on it (++) ? Does it know that it is int because of the container?

There are no complaints when compiling with GCC with -std=c++14 and -std=c++17. Nevertheless, if I compile with -std=c++11 I get the following warning:

lambda_test.cpp: In function ‘int main()’:
lambda_test.cpp:24:51: warning: lambda capture initializers only available with -std=c++14 or -std=gnu++14
  std::generate_n(std::back_inserter(first), 100, [i = 0]() mutable { return i++; });
                                                   ^

MORE: Given the comments, I tried to see a difference on what the compiler produces for c++11 and 14, but it generates the same code: https://cppinsights.io/s/43411e6f


Solution

  • As the error says, you must activate C++14 for lambda capture initializer to work since it was added in C++14.

    As far as I know, I can write the equivalent functionality in this way:

    No, static storage is functionally different. With capture you can copy the lambda and it will copy the state of the captures. With static variables each lambda access the same global.

    How does the compiler know the type of i in the first example? Is it deduced from the operation I perform on it (++) ? Does it know that it is int because of the container?

    No, since many many types has the ++ operator.

    The compiler simply use the initializer to deduce the type. You can see it as if there was a hidden auto there:

    std::generate_n(std::back_inserter(vec), 100, [/* auto */ i = 0]() mutable { return i++; });
    

    The literal 0 is of type int. So i is an int.

    You could technically do this too:

    std::generate_n(std::back_inserter(vec), 100, [/* auto */ i = 0ull]() mutable { return i++; });
    

    Then i has the type unsigned long long, since the literal 0ull is of that type.