I am reading a code snippet from a book and find this:
const char* const & a = \"hello\"; //can compile
const char*& a = \"hello\"; //cannot
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.
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.