10

In the context of a template, the following "reference collapsing" rules are applied:

template <typename T>
void foo(T && t)
{
    //T&  &   -> T&
    //T&  &&  -> T&
    //T&& &   -> T&
    //T&& &&  -> T&&
}

Why does the language prohibit "universal references" from having const qualifiers?

template <typename T>
void foo(T const && t)

It would seem to make sense if the type had resolved to a reference (3 out of the 4 cases).

I'm sure this idea is incompatible with some other design aspect of the language, but I can't quite see the full picture.

M.M
  • 138,810
  • 21
  • 208
  • 365
Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • Why wouldn't you just use `const T&`? – user2357112 Sep 03 '16 at 05:51
  • 1
    I suspect that `const&&` would be no different from `const&`. Temporaries (r-values) can already be bound to `const&` so what can `const&&` add? – Galik Sep 03 '16 at 05:52
  • 1
    AFAIK, this does already happen for references; if your `T` is a `U const &`, then `T &&` will collapse to `U const &`. That term "universal reference" really does mean *universal* reference: you don't need to specify `const` in there to get a constant reference. – yzt Sep 03 '16 at 05:55
  • 1
    Maybe because moving implies some sort of "destruction" of the source object (i.e. which was passed as `T&&`), and you cannot destroy anything that is const. – Sergey Sep 03 '16 at 05:55
  • 1
    By the way, I'm not sure how it interacts with reference collapsing, but [`const T&&` is actually legal, and the standard library even makes use of it](http://en.cppreference.com/w/cpp/utility/functional/ref). – user2357112 Sep 03 '16 at 05:55
  • https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers `Even the simple addition of a const qualifier is enough to disable the interpretation of “&&” as a universal reference`. i couldn't find on the page why it prohibits it though – DevGuy Sep 03 '16 at 05:56
  • @Sergey *cannot destroy anything that is const* is not true. You can create a `const` object in any scope. It will be destroyed when the object gets out of scope. – R Sahu Sep 03 '16 at 06:06
  • @user2357112 also see [What is use of the ref-qualifier `const &&`?](http://stackoverflow.com/q/24824432/1708801) – Shafik Yaghmour Sep 03 '16 at 06:08
  • @RSahu: what Sergey meant is that `&&` is typically used with move semantics, where the recipient takes ownership of the source's resources and then *resets the source* to a stable but invalid state before it gets destroyed after return. You can't modify something if it is `const`. "destroy" is the wrong term to use in relation to `&&`. – Remy Lebeau Sep 03 '16 at 06:15
  • @RemyLebeau, true. That doesn't explain why the recipient can't be a `const` object after it has taken over the ownership of all the resources from the source object. – R Sahu Sep 03 '16 at 06:19
  • @RSahu: um, because a `const` object cant be modified, so the recipient cant be `const` or else it can't take ownership of anything to begin with. – Remy Lebeau Sep 03 '16 at 07:40
  • @RemyLebeau The recipient can be `const`, the donor can't. `const vector recipient = std::move(donor);` – Oktalist Sep 03 '16 at 13:22
  • changed title/tags to use standard terminology ([see here](http://stackoverflow.com/a/33904463/1505939) for discussion) – M.M Sep 03 '16 at 14:34

4 Answers4

4

Originally the rvalue reference proposal said that the transformation happens if P is "an rvalue reference type". However, a defect report later noticed

Additionally, consider this case:

template <class T> void f(const T&&);
...
int i;
f(i);

If we deduce T as int& in this case then f(i) calls f<int&>(int&), which seems counterintuitive. We prefer that f<int>(const int&&) be called. Therefore, we would like the wording clarified that the A& deduction rule in 14.8.2.1 [temp.deduct.call] paragraph 3 applies only to the form T&& and not to cv T&& as the note currently implies.

There appears to have been a time period where const T &&, with T being U&, was transformed to const U&. That was changed to be consistent with another rule that says that const T, where T is U& would stay U& (cv-qualifiers on references are ignored). So, when you would deduce T in above example to int&, the function parameter would stay int&, not const int&.

In the defect report, the reporter states "We prefer that f<int>(const int&&) be called", however provides no reason in the defect report. I can imagine that the reason was that it seemed too intricate to fix this without introducing inconsistency with other rules, however.

We should also keep in mind that the defect report was made at a time where rvalue references could still bind to lvalues - i.e const int&& could bind to an int lvalue. This was prohibited only later on, when a paper by Dave & Doug, "A Safety Problem with RValue References", appeared. So, it seems to me that a deduction that works (at that time) was worth more than a deduction that simply was counter intuitive and dropped qualifiers.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
2

This does already happen for references; if your T is a U const &, then T && will collapse to U const &. The term "universal reference" really does mean universal reference: you don't need to specify const in there to get a constant reference.

If you want to have a truly universal reference mechanism, you need your T && to be able to become all kinds of references, will all kinds of constness. And, T && does exactly that. It collapses to all four cases: both l- and r-value references, and both const and non-const.

Explained another way, the constness is an attribute of the type, not the reference, i.e. when you say T const &, you are actually talking about a U &, where U is T const. The same is true for && (although an r-value reference to a const is less useful).

This means that if you want your universal reference to collapse to a U const &, just pass it something that is of the type you want: a U const &, and it will collapse to exactly that.

To answer you question more directly: the language does not "prohibit" the use of const in the declaration of a universal reference, per sé. It is saying that if you change the mechanism for declaring a universal reference even a little bit - even by inserting a lowly const between the T and the && - then you won't have a (literally) "universal" reference anymore, because it just won't accept anything and everything.

yzt
  • 8,873
  • 1
  • 35
  • 44
0

Why do you think the language does not allow const r-value references?

In the following code, what will be printed?

#include <iostream>

struct Foo 
{
  void bar() const & 
  {
    std::cout << "&\n";
  }

  void bar() const &&
  {
    std::cout << "&&\n";
  }
};

const Foo make() { 
  return Foo{}; 
}

int main()
{
  make().bar();
}

answer:

&&

why? Because make() returns a const object and in this context it's a temporary. Therefore r-value reference to const.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • OP question was poorly phrased, he is asking why the code`template void f(T const &&)` does not allow `T` to deduce to reference type (i.e. does not have forwarding reference behaviour) – M.M Sep 03 '16 at 14:35
-2

Template argument deduction has a special case for "rvalue reference to cv-unqualified template parameters". It is this very special case that forwarding/universal references rely on. See section "Deduction from a function call" in the linked article for details.

Note that before template argument deduction, all top-level cv-qualifiers are removed; however, references never have top-level cv-qualifiers and above rule does not apply, so the special rule also does not apply. (In contrast to pointers, there is no "const reference", only "reference to const")

acs
  • 84
  • 4
  • there is no such thing as a reference with top-level const, so your explanation is hard to understand – M.M Sep 03 '16 at 14:36
  • There are such thing as const pointers, so not sure what the analogy is about . Also the "all top-level cv-qualifiers are removed" only applies to deducing the type of arguments corresponding to a parameter that is NOT a reference. (so, nothing to do with this question) – M.M Sep 04 '16 at 06:53
  • There ARE const pointers, in the same way as there are const int's! And "all top-level cv-qualifiers are removed" does not apply to references, BECAUSE references do not have top-level cv-qualifiers. At least, I do not know of any other reason. – acs Sep 04 '16 at 07:31
  • Yes I just said there are const pointers, so your analogy doesn't work – M.M Sep 04 '16 at 07:38