when will there be && + && -> && with c++ reference collapse?

112 Views Asked by At

I have some question about understanding reference collapse, so I do experiments in the following code:

template<typename T>
void func(T&& t) {}


int main() {
    // There is no reference collapse here, only binding left or right values to
    // references with universal reference:
    int a = 10;
    func(a); // this equals func<int>(a)
    func(10); // what does this equal to?
    func(std::move(a)); // what does this equal to?

    // The following is not case of universal reference, they are reference
    // collapse, only that the type is template type:
    int& b1 = a;
    int&& b2 = 10;

    // this is the collapse of & + && -> &
    func(b1); // this equals func<int&>(b1)

    // this is the collapse of & + && -> &, since b2 is left reference here not
    // right reference:
    func(b2); // this equals func<int&&>(b2)
}

Am I correct on understanding the above code? Would you please show me the case of collapsing && + && -> && ?

2

There are 2 best solutions below

14
On BEST ANSWER

You have some misunderstanding with forwarding reference. When being passed lvalues to func, the template parameter T will be deduced as lvalue-reference; for rvalues T will be deduced as non-reference type. I.e.

int a = 10;
func(a);  // T is deduced as int&, then int& && -> int& is the type of parameter t
func(10); // T is deduced as int, then int&& is the type of parameter t
func(std::move(a)); // T is deduced as int, then int&& is the type of parameter t

int& b1 = a;
int&& b2 = 10;
func(b1); // T is deduced as int&, then int& && -> int& is the type of parameter t
func(b2); // T is deduced as int&, then int& && -> int& is the type of parameter t. Note that b2 is an lvalue

Would you please show me the case of collapsing && + && -> && ?

You can

func<int&&>(0); // T is specified as int&&, then int&& && -> int&& is the type of parameter t
0
On

Your examples for collapsing aren't actually reference collapsing. Template argument deduction of forwarding references (that all of your examples actually do rely on) uses different verbiage that is parallel to, but not dependent on reference collapsing.

std::add_rvalue_reference and std::add_lvalue_reference are the prime examples for reference collapsing.

std::add_rvalue_reference<int&>::type x = ...; // x is a lvalue reference & + && = &
std::add_rvalue_reference<int&&>::type y = ...; // y is a rvalue reference && + && = &&

The standard verbiage is is over at [dcl.ref]/6:

If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier ([dcl.type.decltype]) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR.

[Note 3: This rule is known as reference collapsing. — end note]