Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload resolution with rvalue reference to const char *

#include <iostream>

using namespace std;

void f(const char * const &s) {
    cout << "lvalue" << endl;
}

void f(const char * const &&s) {
    cout << "rvalue" << endl;
}

int main()
{
    char s[] = "abc";

    f("abc");
    f(s);
}

Output:

rvalue
rvalue

Why isn't the output "rvalue lvalue"?

like image 275
alice Avatar asked Feb 23 '15 18:02

alice


2 Answers

Neither the string literal nor s are pointers (they are arrays), so the relevant section of the standard is [conv.array]:

An lvalue or rvalue of type "array of N T" or "array of unknown bound of T" can be converted to a prvalue of type "pointer to T". The result is a pointer to the first element of the array.

Note that

char const *p = s;
f(p);

prints "lvalue," to show that this works as you expect for pointers.

Addendum re: comment: In the case of

char *p = s;
f(p);

which prints "rvalue" if the rvalue overload exists but does not cause a compiler error if it is removed, two other sections of the standard come into play -- one of which seems to prohibit the binding of char* to char const *const & altogether, and the other opening a window back in.

The first is [dcl.init.ref]/4, where it is stated that

Given types "cv1 T1" and "cv2 T2", "cv1 T1" is reference-related to "cv2 T2" if T1 is the same type as T2, or T1 is a base-class of T2. "cv1 T1" is reference-compatible with "cv2 T2" if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2. (...)

It goes on at length about precise rules for reference initialization, all of which is relevant but unfortunately far too long for a SO answer. The long story made short is that a reference to cv1 T1 can be initialized with an object of cv2 T2 if the two are reference-compatible.

What this Legalese means for our case is that char* and char const * are not reference-compatible (although char* and char *const would be), because char* is not char const * nor is one a base class of the other. This restriction makes sense if you consider the following illegal piece of code that would be legal otherwise:

const char c = 'c';
char *pc;
const char*& pcc = pc;   // #1: not allowed
pcc = &c;
*pc = 'C';               // #2: modifies a const object

This is adapted from a similar example in [conv.qual]/4 that uses a pointer to pointer to demonstrate the same problem.

[conv.qual] is also the other relevant section that opens the window back in. It says in [conv.qual]/1:

A prvalue of type "pointer to cv1 T" can be converted to a prvalue of type "pointer to cv2 T" if "cv2 T" is more cv-qualified than "cv1 T"

It follows from all this that char* can be converted to char const *1 (which is reference-compatible with char const *const), which is why the code still compiles if the rvalue overload of f is removed. However, the result of this conversion is a prvalue, so if it is present, the rvalue overload is preferred in overload resolution.

1char* glvalue -> char* prvalue (by [conv.lval]) -> char const * prvalue)

like image 62
Wintermute Avatar answered Nov 17 '22 21:11

Wintermute


s is an array lvalue (so is "abc", for that matter - string literals are lvalues). To get a pointer, the array-to-pointer conversion is performed. This conversion yields a pointer prvalue, which preferentially binds to the rvalue reference overload.

like image 25
T.C. Avatar answered Nov 17 '22 23:11

T.C.