c++templatesreferenceconstantsforwarding-reference

Why is the const Qualifier Ignored When Applied to a Deduced Lvalue Reference in C++?


When applying const to a T that is deduced as an Lvalue reference (for example, when passing an Lvalue to a function that takes universal reference), the modifier is ignored. Suppose we pass a regular Lvalue int, const T which should be converted to const int& is being converted to just int&.

Here is an example:

#include <iostream>

template<typename T>
const T foo(T&& a)
{
    // const T b{ 1 }; This would result in compile-error, cause b is just int&
    const T b{ a };
    return b;
}

int main()
{
    int x = 5;
    foo(x);
}

Here is what I got in C++ insights:

#include <iostream>

template<typename T>
const T foo(T && a)
{
  const T b = {a} /* NRVO variable */;
  return b;
}

/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int & foo<int &>(int & a)
{
  int & b = {a}; // As you see I'm getting an int& here and in return type also
  return b;
}
#endif


int main()
{
  int x = 5;
  foo(x);
  return 0;
}

You can solve this issue by applying std::remove_reference to the T, and then adding the const and reference, like so:

#include <iostream>
#include <type_traits>

template<typename T>
const std::remove_reference_t<T>& foo(T&& a)
{
    const std::remove_reference_t<T>& b{ a };   
    const std::remove_reference_t<T>& c{ 5 }; // Can use Rvalues now
    return b;
}

int main()
{
    int x = 5;
    foo(x);
} 

I was just wondering if this behavior has some explanation? Where else can this behavior rise up? If I'm missing something, I'd appreciate your comments.


Solution

  • It helps to think in east const terms. Qualifiers like const associate to the left except when there is nothing to the left - but const T and T const actually mean the same thing. const applies to the whole T looking at it from the right (east) side.

    When T is int& then const T becomes int& const, which is rather pointless since a reference is immutable anyway.

    Had it been pointers, it would become clearer. If T is int* then const T and T const becomes int* const which is an immutable pointer, but the object it is pointing at is not const and can be changed:

    using T = int*;
    
    int x = 10;
    const T b = &x;
    *b = 20;   // x is now 20
    // b = &x; // error, the pointer is const so you can't assign to it