Okay, so the thing to note here is that overload resolution only ever considers one conversion function for i
. They don't both participate, and so the reference qualifier cannot be used to differentiate them. For the case of binding a reference
[over.match.ref]
Under the conditions specified in [dcl.init.ref], a reference can be
bound directly to the result of applying a conversion function to an
initializer expression. Overload resolution is used to select the
conversion function to be invoked. 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:
- The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
and yield type “lvalue reference to cv2 T2” (when initializing an
lvalue reference or an rvalue reference to function) or “cv2 T2” or
“rvalue reference to cv2 T2” (when initializing an rvalue reference or
an lvalue reference to function), where “cv1 T” is
reference-compatible with “cv2 T2”, are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type “lvalue reference to cv2 T2” (when
initializing an lvalue reference or an rvalue reference to function)
or “rvalue reference to cv2 T2” (when initializing an rvalue reference
or an lvalue reference to function), where T2 is the same type as T or
can be converted to type T with a qualification conversion, are also
candidate functions.
According to the text in bold, when initializing i
, our only candidate is operator int const&
. So overload resolution can either pass here, or fail entirely. But it cannot select operator int
, since that one is not even under consideration. It succeeds because a const qualified lvalue reference can bind to the object argument.
On the other hand, for initializing a value
[over.match.conv]
Under the conditions specified in [dcl.init], as part of an
initialization of an object of non-class type, a conversion function
can be invoked to convert an initializer expression of class type to
the type of the object being initialized. Overload resolution is used
to select the conversion function to be invoked. Assuming that “cv1 T”
is the type of the object being initialized, and “cv S” is the type of
the initializer expression, with S a class type, the candidate
functions are selected as follows:
- The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
and yield type T or a type that can be converted to type T via a
standard conversion sequence are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type T or a type that can be converted
to type T with a qualification conversion are also candidate
functions. Conversion functions that return a cv-qualified type are
considered to yield the cv-unqualified version of that type for this
process of selecting candidate functions. A call to a conversion
function returning “reference to X” is a glvalue of type X, and such a
conversion function is therefore considered to yield X for this
process of selecting candidate functions.
So when initializing j
both conversion functions participate as overloads, and here the reference qualifier makes a difference.
You do get a dangling reference here, and it seems to be due to a dark corner in the language. The bullet in the first quoted paragraph could probably be refined to consider the binding of const lvlaue references better. Since those may bind to temporaries as well, your second conversion operator could ideally be a candidate under better rules.