61
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);
}

Please note: b is of const T& and ++b is ok!

Why is const T& not sure to be const?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
xmllmx
  • 39,765
  • 26
  • 162
  • 323

2 Answers2

66

Welcome to const and reference collapsing. When you have const T&, the reference gets applied to T, and so does the const. You call g like

g<int&>(n);

so you have specified that T is a int&. When we apply a reference to an lvalue reference, the two references collapse to a single one, so int& & becomes just int&. Then we get to the rule from [dcl.ref]/1, which states that if you apply const to a reference it is discarded, so int& const just becomes int& (note that you can't actually declare int& const, it has to come from a typedef or template). That means for

g<int&>(n);

you are actually calling

void f(int& a, int& b)

and you are not actually modifying a constant.


Had you called g as

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

then T would be int, and f would have been stamped out as

void f(int a, const int& b)

Since T isn't a reference anymore, the const and the & get applied to it, and you would have received a compiler error for trying to modify a constant variable.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 7
    This is why the type traits like [`std::add_lvalue_reference`](https://en.cppreference.com/w/cpp/types/add_reference) exist, to ensure that references are added in a predictable way to prevent just this sort of pain. – Mgetz Feb 01 '19 at 14:00
  • 10
    One way to make it easier to understand is to write `T const&` instead of `const T&` (which is the same), and then replace `T` with `int&`. – Ruslan Feb 01 '19 at 15:38
  • 1
    I would reorder some of the first part of this answer, since in the type `const T&`, first the `const` applies to `T`, and then the lvalue-reference applies to the result of that. (If the rule were the opposite, the "const applied to a reference type is ignored" rule would always kick in, and `const T&` would always mean the same as `T&`.) – aschepler Feb 01 '19 at 22:22
  • @aschepler The rules stop `T& const`, not `const T&`/`T const &` – NathanOliver Feb 01 '19 at 22:24
  • @NathanOliver I'm just suggesting discussing the effect of `const` before the effect of lvalue-reference, since `using U = const T&;` means the same as `using tmp1 = const T; using U = tmp1&;` but not the same as `using tmp2 = T&; using U = const tmp2;` – aschepler Feb 01 '19 at 22:30
  • I have to wonder if this is a variation on the issue addressed in this article: http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835 . Granted, the article addresses `typedefs` while this is template types, but the same underlying issue exists. `Tptr` in the article and `T` here are "atomic" types in that while they do contain an indirection/ reference, you can't inject a `const` between the underlying type and the indirection/reference. – dgnuff Feb 02 '19 at 02:36
  • @dgnuff yes, it is the same. This happens when the type is "wrapped" in a single name. – NathanOliver Feb 02 '19 at 02:54
  • 1
    @NathanOliver As I read this answer, you have stated that the reference collapsing rule "happens" before the const collapsing rule, which I think is wrong. If you have `int & const &`, then you need to apply the const rule first to get `int & &`, then the reference rule to get `int&`. I agree that the first bit needs rewording. Maybe an English "translation" is also in order: "The argument to `g` is a constant reference to `T`. The argument to `g` is a constant reference to a reference to `int`. A constant reference to a reference is just a reference. This is formalized in C++ by ..." – HTNW Feb 02 '19 at 03:13
-3

I know that there is already an accepted answer which is correct but just to add to it a little bit, even outside the realm of templates and just in function declarations in general...

( const T& ) 

is not the same as

( const T )

In your example which matches the first, you have a const reference. If you truly want a const value that is not modifiable remove the reference as in the second example.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 10
    when `T` is `int&`, `const T&` and `const T` both give `int&`. – Ben Voigt Feb 01 '19 at 14:52
  • I think there was a misconception on what I was trying to say; I'm using T here not as a `template` parameter. T was just meant to be used as any data type: `int`, `float`, `double` etc.. so `T` in my example above should never be `int&`. I did specifically state outside the realm of templates. – Francis Cugler Feb 01 '19 at 22:08
  • 1
    Hmm, it sounds as if you are explaining that you can see this without templates, which is no problem. But then your final sentence claims to offer a solution to OP's problem, and that problem definitely involves templates. It's fine to offer a solution to a template question which is broader than just templates. But a solution to a template question that isn't accurate for templates, seems not to answer the question. – Ben Voigt Feb 01 '19 at 22:16
  • This can also be an issue with no templates involved: `using T = int&; void f(const T&);` declares `void f(int&);`. – aschepler Feb 01 '19 at 22:23
  • @aschepler true, but I wasn't referring to `using` clauses; just basic function declarations in general. – Francis Cugler Feb 02 '19 at 00:20