Почему "const T &" не обязательно будет const?

template<typename T>
void f(T a, const T& b)
{
    ++a; // ok
    ++b; // also ok!
}

template<typename T>
void g(T n)
{
    f<T>(n, n);
}

int main()
{
    int n{};
    g<int&>(n);
}

Обратите внимание: b имеет постоянную const T& и ++b в порядке!

Почему const T& не обязательно будет const?

Ответ 1

Добро пожаловать в const и справку рушится. Когда у вас есть const T&, ссылка применяется к T, как и const. Вы называете g как

g<int&>(n);

так что вы указали, что T является int&. Когда мы применяем ссылку на ссылку lvalue, две ссылки сворачиваются в одну, поэтому int& & становится просто int&. Затем мы получаем правило из [dcl.ref]/1, которое гласит, что если вы применяете const к ссылке, она отбрасывается, поэтому int& const просто становится int& (обратите внимание, что вы не можете фактически объявить int& const, он должен приходят из typedef или шаблона). Это значит для

g<int&>(n);

ты на самом деле звонишь

void f(int& a, int& b)

и вы на самом деле не изменяете константу.


Если бы вы назвали g как

g<int>(n);
// or just
g(n);

тогда T будет int, а f будет выбито как

void f(int a, const int& b)

Поскольку T больше не является ссылкой, к ней применяются const и &, и вы бы получили ошибку компилятора за попытку изменить постоянную переменную.

Ответ 2

Я знаю, что уже есть принятый ответ, который является правильным, но просто добавить к нему немного, даже за пределами области шаблонов и просто в объявлениях функций в целом...

( const T& ) 

это не то же самое, что

( const T )

В вашем примере, который соответствует первому, у вас есть константная ссылка. Если вы действительно хотите, чтобы значение const не изменялось, удалите ссылку, как во втором примере.