Covariant return type and type conversion
Asked Answered
H

2

9

s->duplicate() returns an object of type Box*, but I'm getting an error initializing it with Box*. It looks like it's being converted back to Shape*. What is the point of having covariant return types if it's converted back to the base class pointer?:

struct Shape
{
    virtual Shape* duplicate()
    {
        return new Shape;
    }
};

struct Box : Shape
{
    virtual Box* duplicate()
    {
        return new Box;
    }
};

int main()
{
    Shape* s = new Box;
    Box*   b = s->duplicate();
}

Error:

main.cpp:22:12: error: cannot initialize a variable of type 'Box *' with an rvalue of type 'Shape *'
    Box*   b = s->duplicate();
           ^   ~~~~~~~~~~~~~~
1 error generated.
Harvestman answered 25/1, 2015 at 3:20 Comment(0)
N
11

Although Box::duplicate is being invoked at runtime (via virtual dispatch), and although Box::duplicate does override Shape::duplicate (covariantly), and although Box::duplicate does return a Box*, you'll still get a Shape* pointer because you are calling duplicate() through a Shape* pointer, and Shape* is the return type of Shape::duplicate(), and the compiler only sees you calling Shape::duplicate, not Box::duplicate.

C++ is not able to dynamically select types, so this is the best it can do. Your Box* is being automatically converted to a Shape* on the way out of Box::duplicate. As Barry said, "it still has to compile at compile time, and at compile time all we know is that it returns a Shape*".

Then, to make it into a Box* again, you need to explicitly cast it (using static_cast or dynamic_cast) because no implicit down-conversion exists.

[C++11: 10.3/7]: The return type of an overriding function shall be either identical to the return type of the overridden function or covariant with the classes of the functions. [..]

[C++11: 10.3/8]: If the return type of D::f differs from the return type of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D. When the overriding function is called as the final overrider of the overridden function, its result is converted to the type returned by the (statically chosen) overridden function (5.2.2). [..]

In the standard text, a pertinent example follows.

Nullify answered 9/5, 2015 at 20:44 Comment(3)
They really couldn't have come up with a shorter example to illustrate this section?Schonfield
@Barry: Surprised? :PNullify
@Kad: Not sure what you're trying to say. Did you mean to ask some question about that line of code? That is invalid because it's an attempted implicit base-to-derived conversion, which is not permitted (and I actually said this in the answer). Try Shape* s = new Box(); instead.Nullify
S
5

The point isn't to do this:

Box*   b = s->duplicate();

That obviously can't work since Shape::duplicate() returns a Shape*. The point, rather, is to accept a Box* if you're calling duplicate() on a Box directly:

Box* old = new Box;
Box* b = old->duplicate(); // OK! We know it's a Box
Schonfield answered 25/1, 2015 at 3:28 Comment(4)
Since the dispatch is performed at runtime why isn't the return type obtained from Box::duplicate()?Harvestman
@templateboy Because it still has to compile at compile time, and at compile time all we know is that it returns a Shape*.Schonfield
The solution isn't very generic. What if I don't know what duplicate() returns?Harvestman
@templateboy You know it returns a Shape*. And if you know that Box::duplicate() returns a Box*, then you can use that info? I don't understand what you're getting at.Schonfield

© 2022 - 2024 — McMap. All rights reserved.