Why can const char* const & = "hello" compile?

C++ReferenceConstantsLanguage Lawyer

C++ Problem Overview


I am reading a code snippet from a book and find this:

const char* const & a = "hello"; //can compile 
const char*& a = "hello"; //cannot

All I know is that when initializing a reference, the array to pointer conversion would not take place.

const char* const &, a reference to a const pointer, the pointer points to const char.

const char*& , a reference to a pointer, the pointer points to const char.

So why does adding an extra const, indicating that the pointer is a const, allow it to compile?

C++ Solutions


Solution 1 - C++

It's essentially adhering to this formula

T const & a = something_convertible_to_T;

Where T is const char*. In the first case, a temporary pointer can be materialized, assigned the address of the literal, and then have itself bound to the reference. In the second case, since the lvalue reference isn't const, it can't happen. Another example of more of the same

const char* && a = "hello"; // rvalue ref makes a no into a yes.

Now the temporary pointer is bound to an rvalue reference.

Solution 2 - C++

Some additional phrasing for the bored, after reading the superb answer by @StoryTeller, as I had to go through a different thought process about this.

So syntactically, In both lines we define a reference a, and in both we shall have a materialization of a temporary pointer that takes the address of the string literal. The only difference between the two is the 2nd const appearing only here:

const char* const & a = "hello";

and not here:

const char*& a = "hello";

This 2nd const denotes that the object being referenced here, a pointer in this case, is itself const, as in it cannot be modified using this reference.

Hence, because the type of this string literal is const char[6] (and not const char * for example), our lvalue reference to type const char* in the second line cannot bind to it -- but the reference in the first line, being a reference to type const char* const could. Why? Because of the rules of reference initialization:

> (5) A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

> * (5.1) If the reference is an lvalue reference and the initializer expression

> * (5.1.1) is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2”, [...] > * (5.1.2) has a class type (i.e., T2 is a class type) [...]

Both the expressions are lvalues, but our “cv1 T1” is not reference-compatible with our “cv2 T2” and “T2” is not a class type.

> * (5.2) Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed

The reference is indeed not const-qualified: our “T1” is const char*, which is a pointer to const , as opposed to a const pointer. The actual type here is a pointer type, so this is what matters.

The Clang error for the second line, read with that in mind, tells us exactly this:

error: non-const lvalue reference to type 'const char *' cannot bind to a value of unrelated type 'const char [6]'
    const char*& a = "hello";
                 ^    ~~~~~~~

The part about being non-const lvalue reference is exactly ¶5.2 -- the lvalue in our case is a pointer to const, but itself isn't const! Unlike the first line. The part about binding to an unrelated type is exactly ¶5.1 -- our const char* isn't compatible with the RHS being const char[6], or const char* const after array to pointer conversion.

For this exact reason, or lack thereof, this can compiles error-free:

char* const & a = "hello";

ISO C++11 warning aside, a compiler lets this one pass (not that it should, as the string literal is a 'const char 6' and we shouldn't be dropping this first const), as the reference is now const with regards to its object, the pointer.

Another interesting thing is that An rvalue reference const char* && a (no "2nd const") could bind to the temporary pointer that has materialized from the string literal, as @StoryTeller provided himself. Why is that? Because of the rules of array to pointer conversion:

> 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".

No mention of const or other cv-qualification phrasing in here, but this stands only as long as we're initializing an rvalue ref.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRickView Question on Stackoverflow
Solution 1 - C++StoryTeller - Unslander MonicaView Answer on Stackoverflow
Solution 2 - C++GeezerView Answer on Stackoverflow