Можно ли предположить, что идентичные лямбда-выражения имеют разные типы?

Я экспериментирую с лямбдами и тем, что разные лямбда-выражения имеют разные типы, хотя они одинаковы. Рассмотрим этот код

#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
}

Это печатает 112, то есть a и b, конечно, одного типа, а c имеет другой тип.

Разрешено ли компилятору c быть того же типа, a?

Я имею в виду, что выражения идентичны, и это будет очевидная оптимизация.

PS: Если захват предотвращает такую оптимизацию, то как насчет лямбда без захвата?

related: что такое подпись типа lambda-функции С++ 11/1y? и может ли выражаться "тип" лямбда-выражения?

Ответ 1

Разрешено ли компилятору c быть того же типа, a?

[&counter](){counter++;} - это лямбда-выражение и в [expr.prim.lambda.closure]/1:

Тип лямбда-выражения (который также является типом объекта замыкания) - это уникальный, неназванный тип неединичного класса, называемый типом замыкания, свойства которого описаны ниже.

Таким образом, для каждого лямбда-выражения, даже если оно идентично предыдущему, вы получите уникальный тип.