6
votes

Can someone explain when rvalue references are preferred over const lvalue references when working as function parameters?

Background: I was trying to pass a const pointer into a function. Since I have to consider the cases in which a local pointer is passed in and in which a temporary is passed in (say from a function call return), I have two choices: the parameter could either be declared as:

void foo(T const* const&); //const lvalue ref to const ptr

or

void foo(T const* &&); //rvalue ref to const ptr

But this rvalue reference cannot be bound to a local variable (which is of lvalue type. But I did remember Scott Meyers coined the term "universal reference" to refer to rvalue reference. This confuses me more.) So my question is, since the first declaration could deal with both cases, when would the second one using rvalue reference be preferred?

Note: In the first approach, the other forms

void foo(const const T* &); 
void foo(const T* const&); 

didn't work. I guess the reason is that in the latter two I was not consistent in the place where the const qualifiers are put into (please correct me if I'm wrong).

3
stackoverflow.com/questions/17005423/… might help, but your question is a bit confusing. You seem to be mixing freely const pointer and pointer to const. If you don't need to perfectly forward or move, rvalue refs aren't really useful.Mat
"universal references" are only when an rvalue reference is used with a template.Rastaban

3 Answers

8
votes

It is very rarely a good idea to pass a pointer by const &: at best it takes the same overhead, at worst it causes extremely complex pointer reseating logic to surprise readers of your code.

Take pointers by value -- T const* -- and things are more sane.

References to non-pointer values make more sense.

Universal references is a technique using rvalue and lvalue references in a type deduction context. It basically only applies when you have a type T&& being deduced from an expression -- in that context T can be X, X& or X const& (or other cv variants).

If T is X& or X const&, the rvalue reference to the lvalue reference collapses into a lvalue reference. It is an example of the standard committee being clever, and it allows auto&&x= based universal reference variables, and perfect forwarding code to be easy to write.

3
votes

Remark: I wrote this answer under the assumption that T in your question represents some actual data type -- I've chosen int in my examples below.

Background: I was trying to pass a const pointer into a function. [...] I have to consider the cases in which a local pointer is passed in and in which a temporary is passed in (say from a function call return)

You didn't say what you mean by "const pointer". I will first assume you mean a pointer that is itself constant (i.e. the address to which it points cannot be changed).

According to your description, there are basically two ways you get such a pointer:

// Case 1 (what you call a local pointer -- this should be inside some
//         function body):
int *const p = 0;

// Case 2, a function that returns a pointer; this is your rvalue case
// in contexts where f() is called and its return value used as a temporary:
int *f()
{ return 0; }

// Note: The temporary returned by this function isn't, strictly speaking,
//       constant. It could be modified as long as it is alive. But from
//       your description I take it that you have no intentions of doing so
//       and/or regard temporaries as generally constant.

Now you can define a function that accepts these two cases as follows:

void g(int *const &arg)
{ }

You can apply this as g(p); to a constant, local pointer such as p defined earlier, as well as to a temporary g(f());. (You could, thirdly, apply it to a non-const local pointer as well, because going from non-const lvalue to const lvalue is never a problem.)

This function g has a function argument arg which is defined as a constant, lvalue reference to an int-pointer. It can bind to a constant (or indeed non-constant) local pointer (such as p) as well as a temporary, because constant lvalue references, unlike non-constant lvalue references, can do that.

Remark: It's not clear to me why, in this case, you need the function argument to be a reference at all. You could simply declare void g(int *const arg) (no ampersand) and do without a reference. Reasons include a) You cannot modify it anyway; b) In all real-world implementations, the reference will take just as much (or as little) space as the pointer itself, so there is no point in avoiding a copy.

Anyway. If you want you can also define a second version of g specifically for rvalue references:

void g(int *&& arg)
{ }

This can only be applied to the temporary, not to the local pointer, because the function argument is defined as an rvalue reference, which can bind to temporaries, but not to lvalues.

However, if by "const pointer" you actually mean a pointer-to-const, i.e. a pointer that can be changed to different addresses, but does not have the power to modify the value stored at those addresses, the declarations are a bit different. The keyword const must then be put before the asterisk, and for better clarity best before the type specifier int:

// Declare local pointer-to-const:
const int *p = 0;

// Function that returns a pointer-to-const:
const int *f()
{ return 0; }

A function that can accept these two would then be declared as:

void g(const int *const &arg)
{ }

The first const means we are talking about pointers-to-const, and the second const ensures we have a constant lvalue-reference, which can bind to both rvalues and lvalues. Note that this function can not modify what arg points to, because arg is declared as a constant lvalue reference. In the case where arg binds to the temporary, that is probably what we want anyway (as stated above). But in the case where the function is called as g(p);, we might actually want to modify the local pointer p from within g. If you want g to have this power, you need to define two versions of it:

void g(const int *&& arg)
{ /* Can bind to temporaries, but not modify them. */ }

void g(const int *& arg)
{ /* Can bind to local variables and modify what they point at */ }

Remark 1: Your original declaration const int *const &const is useless (and not even accepted by GCC). It would mean a "constant reference to a constant pointer to constant int", but since a reference to a constant pointer is implicitly itself a const-reference, the final const is superfluous (and not provided for by the Standard).

Remark 2: Universal references are not the same as rvalue references. Universal references are declared as T &&arg where T is a template parameter. Depending on what T refers to in each instantiation of the template, this may be an lvalue reference or an rvalue reference -- hence its "universal" character. This has nothing to do with your use case, anyway, though, since you are dealing with pointers T * here (even if we assume that T is a template parameter).

2
votes

You do not want to differentiate between a temporary pointer and an lvalue pointer. That looks to me like something that's bound to fail rather sooner than later.

Universal Refs only apply in template functions like

 template<class T> void foo(T && fwdref);

Note that a "universal ref" is not the same as an rvalue ref.