Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

covariant return types with multiple inheritance. how does this code work?

Can anyone tell me how does return type covariance work in the following code?

class X
{
public:
    int x;
};

class Y: public OtherClass, public X
{
};


static Y inst;

class A {
public:
    virtual X* out() = 0;
};

class B : public A
{
    public:
    virtual Y* out() 
    {
         return &inst;
    }
};



void main()
{
    B b; 
    A* a = &b;
    //x and y have different addresses. how and when is this conversion done??
    Y* y = b.out();
    X* x = a->out();
}

EDIT: I'm sorry I must not have been clear enough. x and y point to different addresses just like I expect since there's multiple inheritance involved so X and Y objects are not on the same address. My question is when is this cast being done? the out() function couldn't have done this because it always returns a pointer to Y from its point of view. The caller of out() couldn't have done this because it sees X* whose concrete type might be X or Y. When is the cast done then?

like image 526
Leo Avatar asked Jul 28 '11 09:07

Leo


People also ask

How does covariant return type work in Java?

Covariant return type refers to return type of an overriding method. It allows to narrow down return type of an overridden method without any need to cast the type or check the return type. Covariant return type works only for non-primitive return types.

How is covariant return types implemented?

How is Covariant return types implemented? Java doesn't allow the return type-based overloading, but JVM always allows return type-based overloading. JVM uses the full signature of a method for lookup/resolution. Full signature means it includes return type in addition to argument types.

What are covariant return types What rules apply to return types for overridden methods?

October 12, 2021. In an object-oriented programming language, return type covariance means that a method's return type can be replaced with a narrower one when overridden in a subclass or child class. It eliminates the need to cast or validate the return type when limiting the return type of a custom method.

What is a covariant return type in Java Hackerrank solution?

Java allows for Covariant Return Types, which means you can vary your return type as long you are returning a subclass of your specified return type. Method Overriding allows a subclass to override the behavior of an existing superclass method and specify a return type that is some subclass of the original return type.


3 Answers

By "how does it work", I presume you're asking about what the generated code looks like. (In a typical implementation, of course. We all know that the generated code can vary somewhat between implementations.) I'm aware of two possible implementations:

The compiler always generates code to return a pointer to the base class; i.e. in B::out, the compiler will convert the Y* to an X* before returning it. At the call site, if the call was through an lvalue with static type B, the compiler will generate code to reconvert the returned value to Y*.

Alternatively (and I think this is more frequent, but I'm far from sure), the compiler generates thunks, so when you call a->out, the virtual function which gets called is not directly B::out, but a small wrapper which converts the Y* returned from B::out to an X*.

Both g++ and VC++ seem to use thunks (from a very quick glance).

like image 108
James Kanze Avatar answered Oct 31 '22 13:10

James Kanze


When is the cast done then?

Well, it is done after B::out returns, and before the A::out call ends. There really isn't much to it.


The pointer returned by the polymorphic call must be of type X*. This means it has to point to an object of type X. Since you're using multiple inheritance, your Y objects can have several subobjects in them. In your case they can have an OtherClass subobject and an X subobject. Obviously, these subobjects are not stored in the same address. When you ask for a pointer to X* you get a pointer to the X subobject and when you ask for a pointer to Y* you get a pointer to the whole Y object.

class OtherClass { int a; };

// the other classes

int main()
{
    B b; 
    A* a = &b;
    //x and y have different addresses. how and when is this conversion done??
    Y* y = b.out();
    X* x = a->out();
    OtherClass* o = y;
    std::cout << "x: " << x << std::endl;
    std::cout << "y: " << y << std::endl;
    std::cout << "o: " << o << std::endl;
}

You can see that running this code yields different addresses for the X* and Y* pointers and the same address for the Y* and OtherClass* pointers, because the object was laid out with the OtherClass subobject before the X subobject.

x: 0x804a15c
y: 0x804a158
o: 0x804a158

like image 24
R. Martinho Fernandes Avatar answered Oct 31 '22 14:10

R. Martinho Fernandes


NOTE: first answer is only correct for single inheritance, multiple inheritance below (edit2).

it's normal for x and y to have different addresses because it are two different pointers. They do have the same value though, which is the adress of the variable they're pointing to.

edit: you can use this main to check what I mean, the first line will print the value of xand y (i.e. the address they're pointing to) which always should be the same because actually the same method will be called due to out being virtual. The second will print their own address, which are of course different because they're different (pointer) variables.

#include <iostream>
int main()
{
    B b;
    A* a = &b;
    //x and y have different addresses. how and when is this conversion done??
    Y* y = b.out();
    X* x = a->out();
    std::cout << y << std::endl << x << std::endl;
    std::cout << &y << std::endl << &x << std::endl;
    return 0;
}

edit2: okay, this was wrong, my apologies. When multiple inheritance comes into play the implicit cast from Y* to X* (so at the assignment of x, not the return of out) will change the pointer address.

This happens because at implementation, the layout of Y contains 2 additional class implementations, being the part it inherited from OtherClass and the part it inherited from X. When implicitly casting to X* (which is allowed) the address has to change of course to point to the X-part of Y as X does not know of OtherClass.

like image 1
KillianDS Avatar answered Oct 31 '22 12:10

KillianDS