c++const-reference

Binding const reference to another type


How to know if you can bind a const reference T1 to T2 ?

I used to think that you can bind const reference T1 to type T2 only if T2 is convertible to T1. But since the following compiles:

char x[10];
const char (&y)[10] = x;

that should not be the case, since char[10] is not convertible to const char[10] (correct me if I'm wrong). So, what are the rules for being able to bind const references to different types ? Is there just an additional rule like: for any type T you can bind a const reference T to it ?


Solution

  • The standard describes the reference binding rules in [dcl.init.ref]/4 and [dcl.init.ref]/5. There is a rather long list of rules, but the bits most relevant to your question are:

    [dcl.init.ref]/4:

    Given types “cv1 T1” and “cv2 T2”, “cv1 T1” is reference-related to “cv2 T2” if T1 is similar ([conv.qual]) to T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if a prvalue of type “pointer to cv2 T2” can be converted to the type “pointer to cv1 T1” via a standard conversion sequence ([conv]).

    [dcl.init.ref]/5:

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

    — If the reference is an lvalue reference and the initializer expression
        — is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2”, or
        [...]
    then the reference binds to the initializer expression lvalue [...]

    In your case, T1 would be const char [10] and T2 would be char [10]. T1 is reference-compatible with T2 because T2* can be converted to T1*, as it only requires adding const-qualification to the pointed type, which is a standard conversion.

    As you can see in the referenced sections, this is not the only case where reference binding is allowed - another case, for example, is binding a reference to a result of conversion (including user-defined). const references are also special as they are allowed to bind to rvalues.

    Note that this is indeed different from your previous understanding - T2 may not be convertible to T1 while you may be able to bind a reference still. Here's an example:

    struct A
    {
        A(int);
        A(A const&) = delete;
    };
    
    struct B : A
    {
        B() : A(10) {}
    };
    
    B b;
    A& ra = b; // ok, binds to base subobject A of b
    A a = b;   // fail, A cannot be constructed from an object of B