Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this reference binding ill-formed?

Consider this code:

int **p = 0;

class S {
public:
    explicit operator int**&() {
        return p;
    }
};

int main() {
    S a;
    int *const*&b (a); // error in both g++-7 and clang-5.0 with "-std=c++17"
    return 0;
}

You will agree

  • a qualification conversion from int** to int*const* is possible, and
  • int *const*&b (a) is a direct-initialization.

First, we refer to 11.6.3, paragraph 5 [dcl.init.ref] from n4700.

A reference to type “cv1 T1 (= int*const*)” is initialized by an expression of type “cv2 T2 (= S)” as follows:

  • If the reference is an lvalue reference and the initializer expression
    • ...
    • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (16.3.1.6) and choosing the best one through overload resolution (16.3)),

then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case...

Here, we expect T3 to be int*const*. As noted above, whether it's a possible conversion is determined as per 16.3.1.6, paragraph 1 [over.match.ref].

... Assuming that “reference to cv1 T” is the type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

  • ... For direct-initialization, those explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” or “cv2 T2” or “rvalue reference to cv2 T2”, respectively, where T2 is the same type as T or can be converted to type T with a qualification conversion are also candidate functions.

Here, S::operator int**& yields "lvalue reference to T2 (= int**)", and it can be converted to T (= int*const*) by a qualification conversion. Here, we can say that the conversion is possible, but the program is not accepted in both g++-7 and clang-5.0. Why is that?

like image 703
b1sub Avatar asked Dec 27 '17 14:12

b1sub


1 Answers

The reference initialization rule we're looking for is [dcl.init.ref]:

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

We have cv1 T1 as int* const* and cv2 T2 as S. We then go through the next sections carefully:

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
  • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions ([over.match.ref]) and choosing the best one through overload resolution),

then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).

Our reference is an lvalue reference. The initializer expression is an lvalue but the two types are not reference-compatible, so the first bullet does not apply.

The initializer expression does have non-reference-related class type, but it cannot be converted to a reference-compatible type. The reference-compatible part is important. int** is not reference-compatible with int* const*, and while the former can be converted to the latter, the result would not be an lvalue - which is also required.

So, this section doesn't apply, and we move on.

Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.

Our reference meets neither of those criteria, so the initialization is ill-formed.


A simpler version of this failure would be:

int* pi;
int const*& r = pi; // error

We can't go through a qualification conversion when we have an lvalue reference to non-const type.

like image 114
Barry Avatar answered Nov 14 '22 22:11

Barry