Need an example of reference collapsing rule T&&&& -> T&& on VS2017

380 Views Asked by At

Several sources given the C++ reference collapsing rules as follows:

A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&

(e.g. http://thbecker.net/articles/rvalue_references/section_08.html )

I can give the following example for A&&& becomes A&

template <class T> void f1(T&& param) {
    // T t = 5;  // does not compile because T is T&
    param++; // param collapses from int&&&& to int&&
}

void demo()
{
    int x = 8;
    int &y = x;
    f1(y);   // OK T will still be int&, param will go to int&&& -> collapses to int&
    cout << y;  // prints 9
}

I would like something similar for A&& && becomes A&&, however when I call with an RValue , T is deduced to int, and so this does not show what I would like to.

template <class T> void f1(T&& param) {
    T t = 5;  // compiles since T is int
    T t2 = t; // would not compile if T was int&&
    t2++;
    cout << t; // prints 5 since t2 was not a reference
}

void demo()
{
    f1(8);   // OK T deduced to int, param will go to int&& , no collapsing
}

Can someone help me show a similar example where T is deduced to T&& and the parameter is collapsed from T&&&& to T&&?

2

There are 2 best solutions below

0
On

After help from commenters and other answer posters, I've come up with this demo:

template <class T> void f1() {
    int x = 5; // an lvalue
    //T t1 = x; // compiles when T is int or int&, fails when T is int&
    //T& t2 = x; // always compiles, becomes int&. Can check with ++t2; cout << x;
    //T&& t3 = x; // compiles when T is int&, fails when t3 is &&
}

void demo()
{
    //f1<int>();     // T becomes int, t1=int,  t2=int&   t3=int&&
    //f1<int&>(); // T becomes int&  t1=int& t2=int& t3=int&
    f1<int&&>(); // T becomes int&&, t1=int&&  t2=int&, t3=int&&
}
2
On

Can someone help me show a similar example where T is deduced to T&& and the parameter is collapsed from T&&&& to T&&?

If we are talking about actual deduction, where the template argument T is not given explicitly or defaulted somehow, that's impossible. The reason is that forwarding relies not only on reference collapsing.

To begin with, we must note that in C++ an argument to a function (template) is always some sort of expression. And expressions never have reference type.

[expr.type]

1 If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

You can't actually produce an argument to a function that is of reference type. So T cannot be deduced to be a reference under normal circumstances. Forwarding works by also relying on a special case for reference function parameters in templates.

[temp.deduct.call]

3 If P is a reference type, the type referred to by P is used for type deduction. [...] A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

As you can see, a forwarding reference is specifically designed around the value category of the expression supplied as an argument. It is only if the expression is an lvalue, that the type of the argument is taken as "lvalue reference to A". Otherwise, there is no adjustment done. And so T is going to be deduced as the type being referred to.

The reference collapsing rules only come into play for when the type argument is explicitly given. In that case, when int&& is supplied somehow, we get an rvalue reference by virtue of reference collapse. But it is not after template argument deduction.