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

2

5

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 && + && -> && ?

Macule answered 20/9, 2023 at 7:40 Comment(1)
N
6

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
Nicolas answered 20/9, 2023 at 7:46 Comment(14)
Why doesn't func(b2) equal to func<int&&>(b2)?Macule
Also, reference collapse has nothing to do with the rule of universal reference, is this correct?Macule
@coincheung As I explained, b2 is an lvalue (even its type is rvalue-reference, see more about value categories), then func(b2) is same as func<int&>(b2) according to forwarding reference rule.Nicolas
@coincheung Yes they're two independent rules. In the invocation of func, the final result comes from the interaction of these 2 rules.Nicolas
" 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". Is this is core or universal reference? This always works in the case of template<typename T> void func(T&&) {}, right? and it also works in the case of template<typename T> void func(T&) {} ?Macule
@coincheung No, it's only for universal reference (i.e. forwarding reference).Nicolas
Would you please show me a case of && + & -> & ?Macule
@coincheung Not & + && -> &? Maybe template<typename T> void func(T&) {}, then func<int&&>(a);, the parameter type would be && + & -> &.Nicolas
What is the rule of this: template <typename T> void func(T&);, if I call with func(a) and func(10) ?Macule
@coincheung No difference here. T will be deduced as int and parameter type will be int& for both cases. Then func(10); will fail.Nicolas
Why not T will be deduced into int& when call func(a), and what is the process of deducing when calling func(10); ? In universal reference, func(a) is deduced into func(int& &&), but non-universal reference, it will be func(int &), not func(int& &)?Macule
It's just how normal template deduction works, template parameter won't be deduced as reference type. For your 2nd question, as T is deduced as int, then parameter type would be int&. No reference collapse here.Nicolas
One more question, deduction only happens when calling without <> to assign type. For example, func(a) will trigger deduction, but func<int> or func<int&> or func<int&&>() will not trigger type deduction. Am I correct on this?Macule
@coincheung Yes, when you specify template argument explicitly, template deduction won't happen.Nicolas
S
4

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]

Saccharine answered 20/9, 2023 at 7:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.