I've encountered a problem with implicit conversion in C++. The following is a minimal example:
struct A {
virtual void f()=0; // abstract
};
struct Ad : A {
virtual void f() {} // not abstract
};
struct B {
operator Ad () const { return Ad(); }
};
void test(A const &lhs) {}
int main()
{
B b;
test(b);
}
What I would like the compiler to do is: convert b to a variable of type Ad (using the conversion defined in B) and pass the result to test. However, the above code does not compile in GCC (with C++11 enabled), the result being Cannot allocate an object of abstract type 'A'.
Some things to note:
- Clang compiles this.
- If you make A non-abstract by changing
f()=0;
to f() {}
, the code works just fine.
- The compiler does find the conversion operator (as indicated by 2), but it doesn't do what I'd like it to do.
(All quotes from N4140, the C++14 FD)
TL;DR: The code is well-formed, this is (or was) a GCC bug.
The rules for reference initialization are covered in [dcl.init.ref]/5. I'll first show you the bullet that doesn't cover it - if you want to skip that go straight to the third quote.
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.
- If the initializer expression
- is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue 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 xvalue,
class prvalue, or function lvalue of type “cv3 T3
”, where “cv1 T1
” is reference-compatible with “cv3 T3
” (see 13.3.1.6),
then the
reference is bound to the value of the initializer expression in the
first case and to the result of the conversion in the second case (or,
in either case, to an appropriate base class subobject).
And reference-compability is defined in [dcl.init.ref]/41.
Now consider the linked 13.3.1.6:
Under the conditions specified in 8.5.3, a reference can be bound
directly to a glvalue or class prvalue that is 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 “cv1 T
” is the underlying 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
” [..],
where “cv1 T
” is reference-compatible (8.5.3) with “cv2 T2
”,
are candidate functions. For direct-initialization, [..].
As you can see, your conversion function isn't a candidate after this paragraph. Thus the next bullet in [dcl.init]/5 is applicable:
Otherwise:
- If
T1
is a class type, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1
T1
” by user-defined conversion (8.5, 13.3.1.4); the program is
ill-formed if the corresponding non-reference copy-initialization
would be ill-formed. The result of the call to the conversion
function, as described for the non-reference copy-initialization, is
then used to direct-initialize the reference. The program is
ill-formed if the direct-initialization does not result in a direct
binding or if it involves a user-defined conversion.
Note that the phrase "the program is
ill-formed if the corresponding non-reference copy-initialization
would be ill-formed" may imply that as
B b;
A a = b;
is ill-formed, the program is ill-formed. I believe this to be a defect or vagueness in the wording though, and not the reason that GCC does not accept the code. Assuredly the wording solely aims at the initialization itself, not the fact that a most-derived object of type T1
(aka A
) can be created in the first place.
Finally 13.3.1.4 accepts our conversion function:
Assuming that “cv1 T
” is the type of the object being initialized,
with T
a class type, the candidate functions are selected as
follows:
- The converting constructors (12.3.1) of
T
are candidate functions.
- When the type of the initializer expression is a class type “cv
S
”, the non-explicit conversion functions of S
and its base
classes are considered. [..]. Those that are not hidden within S
and yield a type whose cv-unqualified version is the same type as
T
or is a derived class thereof are candidate functions.
Now the last question is whether in
A const& ref(Ad());
ref
is bound directly. And it is.
Thus the original parameter reference binds directly, and no most-derived object of type A
must be created.
What GCC presumably thinks is that a temporary of type A
must be initialized and the reference be bound to that temporary. Or it pedantically follows the above defected wording, which is very unlikely.
1)
Given types “cv1 T1
” and “cv2 T2
,” “cv1 T1
” is reference-related to
“cv2 T2
” if T1
is the same type as T2
, or T1
is a base class of T2
.
“cv1 T1
” is reference-compatible with “cv2 T2
” if T1
is
reference-related to T2
and cv1 is the same cv-qualification as, or
greater cv-qualification than, cv2.