Search code examples
c++c++14type-deductiongeneric-lambda

Generic lambda, type deduction of actual parameter (auto)


Consider:

struct Foo {
    std::vector<int> data () const { return vec; }
    const std::vector<int>& c_data () const { return vec; }

    std::vector<int> vec {};
};

auto lambda1 = [] (const std::vector<int>&) {};
auto lambda2 = [] (std::vector<int>&&) {};

auto lambda3 = [] (const auto&) {};
auto lambda4 = [] (auto&& p) {};

And usage is:

Foo f {};

lambda1 (f.data ());
lambda1 (f.c_data ());

lambda2 (f.data ());
lambda2 (f.c_data ()); // (X)

lambda3 (f.data ());
lambda3 (f.c_data ());

lambda4 (f.data ());
lambda4 (f.c_data ()); // (Y)

This code fails to compile because of (X), which is of course understandable by me. We can not bind const reference, to rvalue reference. Fine.

Could someone explain me what is the actual type of lambda4's p parameter? Here (Y), compiler does compile it even though I pass const reference argument to it.

What is the difference between lambda2 and lambda4 types in the sense of type deduction?


Solution

  • auto lambda2 = [] (std::vector<int>&&) {};
    

    This is roughly equivalent to:

    struct __lambda2 {
        void operator()(std::vector<int>&& ) {}
    } lambda2;
    

    The call operator here takes an rvalue reference - it only accepts rvalues. c_data() gives you an lvalue, hence the compiler error.

    On the other hand,

    auto lambda4 = [] (auto&& p) {};
    

    is roughly equivalent to:

    struct __lambda4 {
        template <class T>
        void operator()(T&& ) {}
    } lambda4;
    

    T&&, where T is a template parameter, is not an rvalue reference to a deduced type (even though that's what it looks like) - it's a forwarding reference. It can accept both lvalues and rvalues - and will deduce T differently depending on the two value categories (T=std::vector<int> in the first call and T=std::vector<int> const& in the second). Since this accepts lvalues, there is no compile error.

    Note that forwarding references only have the form T&& (for template parameter T) and auto&&. Ex.:

    template <class U>
    struct X {
        template <class T> void foo(T&& );      // forwarding reference
        template <class T> void bar(T const&&); // rvalue reference to const T
        void quux(U&& );                        // rvalue reference to U
    };