Why can we avoid specifying the type in a lambda capture?

Viewed 256

Why is the variable 'n' in 2nd usage of std::generate and within lambda capture not preceded with it's data type in below code? I thought it's important to specify the datatype of all identifiers we use in a c++ code.

#include <algorithm>
#include <iostream>
#include <vector>
 
int f()
{ 
    static int i;
    return ++i;
}
 
int main()
{
    std::vector<int> v(5);
    auto print = [&] {
        for (std::cout << "v: "; auto iv: v)
            std::cout << iv << " ";
        std::cout << "\n";
    };
 
    std::generate(v.begin(), v.end(), f);
    print();
 
    // Initialize with default values 0,1,2,3,4 from a lambda function
    // Equivalent to std::iota(v.begin(), v.end(), 0);
    std::generate(v.begin(), v.end(), [n = 0] () mutable { return n++; });
    print();
}
2 Answers

Why is the variable 'n' in 2nd usage of std::generate and within lambda capture not preceded with it's data type in below code?

It's not preceded with a data type, because the grammar of C++ says that it doesn't need to - nor even allowed to - be preceded by a type name.

A type name isn't needed because the type is deduced from the type of the initialiser expression.

From cppreference:

A capture with an initializer acts as if it declares and explicitly captures a variable declared with type auto, whose declarative region is the body of the lambda expression (that is, it is not in scope within its initializer), [...]

Lambdas used the opportunity of a syntax that was anyhow fresh and new to get some things right and allow a nice and terse syntax. For example lambdas operator() is const and you need to opt-out via mutable instead of the default non-const of member functions.

No auto in this place does not create any issues or ambiguities. The example from cppreference:

int x = 4;
auto y = [&r = x, x = x + 1]()->int
    {
        r += 2;
        return x * x;
    }(); // updates ::x to 6 and initializes y to 25.

From the lambda syntax it is clear that &r is a by reference capture initialized by x and x is a by value capture initialized by x + 1. The types can be deduced from the initializers. There would be no gain in requiring to add auto.


In my experience n could have been just declared inside the lambda body with auto or int as datatype. Isnt it?

Yes, but then it would need to be static. This produces the same output in your example:

std::generate(v.begin(), v.end(), [] () mutable { 
    static int n = 0;
    return n++; });

However, the capture can be considered cleaner than the function local static.

Related