I'm puzzled with this behavior of C++:
struct A { virtual void print() const { printf("a\n"); } }; struct B : public A { virtual void print() const { printf("b\n"); } }; struct C { operator B() { return B(); } }; void print(const A& a) { a.print(); } int main() { C c; print(c); }
So, the quiz is, what is the output of the program - a or b? Well, the answer is a. But why?
Physical Data Design Considerations. Polymorphism: Polymorphism (or operator overloading) is a manner in which OO systems allow the same operator name or symbol to be used for multiple operations. That is, it allows the operator symbol or name to be bound to more than one implementation of the operator.
Here, + is a binary operator that works on the operands num and 9 . When we overload the binary operator for user-defined types by using the code: obj3 = obj1 + obj2; The operator function is called using the obj1 object and obj2 is passed as an argument to the function.
Cast operator: () A type cast provides a method for explicit conversion of the type of an object in a specific situation.
A cast is a special operator that forces one data type to be converted into another. As an operator, a cast is unary and has the same precedence as any other unary operator. The most general cast supported by most of the C++ compilers is as follows − (type) expression. Where type is the desired data type.
The problem here is a bug / misfeature / hole in the C++03 standard, with different compilers trying to patch over the problem in different ways. (This problem no longer exists in C++11 standard.)
Sections 8.5.3/5 of both standards specify how a reference is initialized. Here's the C++03 version (the list numbering is mine):
A reference to type
cv1 T1
is initialized by an expression of typecv2 T2
as follows:
If 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) and can be implicitly converted to an lvalue of typecv3 T3
, wherecv1 T1
is reference-compatible withcv3 T3
then the reference is bound directly to the initializer expression lvalue in the first case, and the reference is bound to the lvalue result of the conversion in the second case.
Otherwise, the reference shall be to a non-volatile const type (i.e.,
cv1
shall beconst
).If the initializer expression is an rvalue, with
T2
a class type, andcv1 T1
is reference-compatible withcv2 T2
, the reference is bound in one of the following ways (the choice is implementation-defined):
- The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.
- A temporary of type
cv1 T2
[sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.
Otherwise, a temporary of type
cv1 T1
is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5). The reference is then bound to the temporary.
There are three types involved in the question at hand:
T1
. In this case, it is struct A
.T2
. In this case, the initializer expression is the variable c
, so T2
is struct C
. Note that because struct A
is not reference-compatible with struct C
, it's not possible to directly bind the reference to c
. An intermediate is needed.T3
. In this case, this is struct B
. Note that applying the conversion operator C::operator B()
to c
will convert the lvalue c
to an rvalue.The initializations by what I labeled as 1.1 and 3 are out because the struct A
is not reference-compatible with struct C
. The conversion operator C::operator B()
needs to be used. 1.2 is out Because this conversion operator returns an rvalue, this rules 1.2 out. All that is left is option 4, create a temporary of type cv1 T1
. Strict compliance with the 2003 version of the standard forces the creation of two temporaries for this problem, even though only one will suffice.
The 2011 version of the standard fixes the problem by replacing option 3 with
If the initializer expression
- is an xvalue, class prvalue, array prvalue or function lvalue and
cv1 T1
is reference- compatible withcv2 T2
, or- has a class type (i.e.,
T2
is a class type), whereT1
is not reference-related toT2
, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of typecv3 T3
, wherecv1 T1
is reference-compatible withcv3 T3
,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). In the second case, if the reference is an rvalue reference and the second standard con- version sequence of the user-defined conversion sequence includes an lvalue-to-rvalue conversion, the program is ill-formed.
It appears that the gcc family of compilers chose strict compliance over intent (avoid creating unnecessary temporaries), while the other compilers that print "b" chose intent / corrections to the standard. Choosing strict compliance isn't necessarily commendable; there are other bugs/misfeatures in the 2003 version of the standard (e.g., std::set
) where the gcc family chose sanity over strict compliance.
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