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
int**
to int*const*
is possible, andint *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 “cv2T2 (= 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), whereT1
is not reference-related toT2
, and can be converted to an lvalue of type “cv3T3
”, where “cv1T1
” is reference-compatible with “cv3T3
” (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 “cvS
” is the type of the initializer expression, withS
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 cv2T2
” or “cv2T2
” or “rvalue reference to cv2T2
”, respectively, whereT2
is the same type asT
or can be converted to typeT
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?
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 “cv2T2
” 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With