Behavior of C++ template function
Asked Answered
E

3

18

Let's say I have this function:

bool f(int&& one, int&& two) { }

If I attempt to call it with this code:

int x = 4;
f(x, 5);

the compiler will complain that it cannot convert x from lvalue reference to rvalue reference, which is correct.

Now if I convert f into a template function like this:

template <class T, class U>
bool f(T&& one, U&& two) { }

then I can call it with an lvalue reference:

int x = 5;
f(x, 5);

Why is it so? Why doesn't the compiler complain in this case?

Encomium answered 3/10, 2013 at 7:24 Comment(0)
I
8

Because there is a template argument deduction, reference collapsing happens. It is what Scott Meyers calls universal references. The U&& will actually become int &. There is a nice article and video about how it works and how it could be used.

Incisive answered 3/10, 2013 at 7:31 Comment(0)
K
10

Per § 8.3.3 / 6. It's the reference collapsing rule.

template <class T> void func(T&&)  // Accepts rvalue or lvalue
void func(T&&)                     // Accepts rvalue only
void func(T&)                      // Accepts lvalue only

Worth example from standard draft:

int i;
typedef int& LRI;
typedef int&& RRI;

LRI& r1 = i;           // r1 has the type int&
const LRI& r2 = i;     // r2 has the type int&
const LRI&& r3 = i;    // r3 has the type int&

RRI& r4 = i;           // r4 has the type int&
RRI&& r5 = 5;          // r5 has the type int&&

decltype(r2)& r6 = i;  // r6 has the type int&
decltype(r2)&& r7 = i; // r7 has the type int&
Knowlton answered 3/10, 2013 at 7:28 Comment(2)
@jk.: And there I was thinking I understand most of this world!Procryptic
Not only is it reference collapsing, but also a quirk in the type deduction specifications, see [temp.deduct.call]/3. (Also, I think you're referring to 8.3.2/6, not 8.3.3/6)Frieze
I
8

Because there is a template argument deduction, reference collapsing happens. It is what Scott Meyers calls universal references. The U&& will actually become int &. There is a nice article and video about how it works and how it could be used.

Incisive answered 3/10, 2013 at 7:31 Comment(0)
S
6

This happens because of reference collapsing rules added in c++11

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

In templates these rules are applied but not in a normal function there is no reference collapsing normally in function. There are some other specific situations where reference collapsing will occur like in the presence of auto, decltype or a typedef (that includes using declarations) That explains the results of your compilation. Reference collapsing had to be added in c++11 because otherwise using references like A & & would become errors since you cannot have a reference to a reference.

Saidel answered 3/10, 2013 at 7:28 Comment(2)
While reference collapsing does occur in the case of a "universal reference" the real magic happens before this step and occurs during template argument deduction. Your note about reference collapsing not working in a normal function is simply wrong; reference collapsing can happen anywhere: using T = int&&; void foo(T& x); here x is a int& due to reference collapsing.Widdershins
@Widdershins looks like your right, I already knew that decltypes and auto could have reference collapsing, didn't know that a typedef could cause it, I'll amend my answerSaidel

© 2022 - 2024 — McMap. All rights reserved.