11

I am experimenting with lambdas and the fact that different lambda expressions have different types, even though they are the same. Consider this code

#include <iostream>

template <typename T> void once(T t){
    static bool first_call = true;
    if (first_call) t();
    first_call = false;
}

int main() {    
    int counter = 0;
    auto a = [&counter](){counter++;};
    once(a);
    once(a);
    std::cout << counter;              // 1

    auto b = a;                        // same type
    once(b);
    std::cout << counter;              // 1

    auto c = [&counter](){counter++;}; // different type
    once(c);
    once(c);               
    std::cout << counter;              // 2
}

This prints 112, ie a and b are of course of same type and c has a different type.

Is the compiler allowed to let c be of the same type than a?

I mean the expressions are identical and it would be an obvious optimization.

PS: In case the capture prevents such an optimization, then what about lambdas without capture?

related: what is the type signature of a c++11/1y lambda function? and Can the 'type' of a lambda expression be expressed?

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185

1 Answers1

21

Is the compiler allowed to let c be of the same type than a?

No. [&counter](){counter++;} is a lambda expression and per [expr.prim.lambda.closure]/1:

The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below.

So, for each lambda expression, even if it is identical to a previous one, you will get a unique type.

You can use typeid to check that this is the case like:

#include <iostream>
#include <typeinfo>

template <typename T> void once(T t){
    static bool first_call = true;
    std::cout << typeid(t).name() << std::endl;
    if (first_call) {
        t();
    }
    first_call = false;
}

int main() {    
    int counter = 0;
    auto a = [&counter](){counter++;};
    once(a);
    once(a);
    std::cout << counter << std::endl; // 1

    auto b = a;                        // same type
    once(b);
    std::cout << counter << std::endl; // 1

    auto c = [&counter](){counter++;}; // different type
    once(c);
    once(c);               
    std::cout << counter << std::endl; // 2
}

result:

Z4mainEUlvE_                                                                                                          
Z4mainEUlvE_                                                                                                          
1                                                                                                                     
Z4mainEUlvE_                                                                                                          
1                                                                                                                     
Z4mainEUlvE0_                                                                                                         
Z4mainEUlvE0_                                                                                                         
2

and you can see there are two function template instantiations.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • i have to admit my standardese is really crappy. Is it maybe the case that in general each expression is considered to be unique even if it says the same thing? – 463035818_is_not_an_ai May 18 '18 at 19:15
  • 1
    @user463035818 Well `a += 10;` will always have the same type no matter where you call it. lambdas are a special type of expression as they are just syntactic sugar for writing a functor. So, I wouldn't say every expression is unique, but you are guaranteed that the type of every lambda expression is unique. – NathanOliver May 18 '18 at 19:20
  • 2
    @user463035818 Note that this rule is the reason why you cannot pass any lambda to a non-template function: You cannot write down its type to declare the argument. This inability to pass around lambdas, in turn, is the reason why `std::function<>` exists: It type-erases the unique lambda type, and provides you with a type based on the lambda's signature which you can actually write down. – cmaster - reinstate monica May 18 '18 at 19:29
  • yes `a += 10;` always has the same type, i was refering to `a += 10; a += 10;` being two distinct (unique) expressions. I never considered this before. If in my code the two appearences of `[&counter](){counter++;};` would be considered to be one and the same expression, then the part you quoted would allow them to have the same type, but they dont – 463035818_is_not_an_ai May 18 '18 at 20:33