Search code examples
c++lambdac++14decltypetype-deduction

Why does decltype(auto) not work as expected?


#include <type_traits>
#include <utility>

int main()
{
    auto f1 = [](auto&& e) -> auto
    {
        return e;
    };

    auto f2 = [](auto&& e) -> auto&
    {
        return e;
    };

    auto f3 = [](auto&& e) -> auto&&
    {
        return e;
    };

    auto f4 = [](auto&& e) -> decltype(auto)
    {
        return e;
    };

    int n{};

    f1(std::move(n)); // ok
    f2(std::move(n)); // ok
    f3(std::move(n)); // ok
    f4(std::move(n)); // error
}

clang's error message:

error : rvalue reference to type 'int' cannot bind to lvalue of type 'int'

To me, decltype(auto) has only three possible deduced types:

  1. auto
  2. auto&
  3. auto&&

Why is f4 error while all of the other three are ok?


Solution

  • It's a GCC bug.

    decltype(auto) = e is equivalent to decltype(e) and yield the declared type of e.

    auto works as template parameter which mean that auto&& is the same as T&& (forwarding reference) for an invented template parameter.

    For f1 the return type is deduced to int.

    For f2 the return type auto& is equivalent to T& with deduced T=int which is the type of the lvalue e, here you're binding int& to e.

    For f3 consider this:

    auto&& t = n;
    static_assert(std::is_same_v<decltype(t), int&>); // true
    

    for both the return of f3, auto&& is equivalent to the invented template parameter T&& which is a forwarding reference, this initialized with an lvalue, yield T& with deduced T=int, then again ... binding int& to the lvalue e.

    Now for f4 consider this:

    int&& r = 9;
    static_assert(std::is_same_v<decltype(r), int&&>); // true (1)
    
    decltype(auto) t = r; // fail with the same error you got.
    

    the parameter of f4 is also a forwarding reference T&& that is initialized with the xvalue std::move(n), this deduce T=int resulting in a parameter int&& e. A return type as decltype(auto) with return e means that the actual return is decltype(e), then as you can see (1) is true, the same hold for decltype(e), this means that the actual return of f4 is int&& ... and there's the problem, f4 is trying to bind a rvalue int&& to an lvalue e which is prohibited.

    You can also take a look at @StoryTeller's answer for the GCC bug.