Search code examples
c++standardsc++17decltypestructured-bindings

Why does "const auto [x, y]" not behave as expected when binding to reference types?


The following code snippet is excerpted from cppref:

std::tuple<int, int&> f();

auto [x, y] = f(); 
// decltype(x) is int
// decltype(y) is int&

const auto [z, w] = f();
// decltype(z) is const int
// decltype(w) is int&

My question is at the last line:

Why is decltype(w) int& rather than const int&?


Solution

  • Jarod42 answered the question the question in the comments, let me just cite the relevant part of the standard here, from [dcl.struct.bind]¹:

    Given the type Ti designated by std​::​tuple_­element​::​type, variables are introduced with unique names ri of type “reference to Ti” initialized with the initializer ([dcl.init.ref]), where the reference is an lvalue reference if the initializer is an lvalue and an rvalue reference otherwise. Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti.

    Hence in const auto [z, w] = f();, you have const T1 with T1 being int and const T2 with T2 being int&. As const modifies what's on its left, this becomes int& const and results in int&.

    Note that int& const becoming int& is only possible in template argument substitution, i.e., this won't compile:

    int n = 42;
    int& const doesntWork = n; // Error: 'const' qualifiers cannot be applied to 'int&'
    

    but this does:

    template <class T> void f(const T t)
    {
       ++t;
    }
    
    int n = 42;
    
    f<int&>(n);
    

    where the identical contraction from int& const to int& as above takes place.

    ¹ Thanks to @cpplearner for pointing me to the exact paragraph here.