John
John

Reputation: 3494

How to understand the type returned by the final overrider is implicitly converted to the return type of the overridden function that was called?

As per the document, which says that[emphasis mine]:

When a virtual function call is made, the type returned by the final overrider is implicitly converted to the return type of the overridden function that was called.

How to understand that in the right way?

According to the output of the demo code below, it seems that the type returned by the final overrider depends the static type of the instance (which the member function would be invoked on).

If I miss something, please let me know.

Here is the demo code snippet:

#include<iostream>
#include<typeinfo>

#define print() std::cout << __PRETTY_FUNCTION__ << std::endl;

class B {};
 
struct Base
{
    virtual void vf1(){print();};
    virtual void vf2(){print();};
    virtual void vf3(){print();};
    virtual B* vf4(){print();};
    virtual B* vf5(){print();};
};
 
class D : private B
{
    friend struct Derived; // in Derived, B is an accessible base of D
};
 
class A; // forward-declared class is an incomplete type
 
struct Derived : public Base
{
    void vf1(){print();};    // virtual, overrides Base::vf1()
    void vf2(int){print();}; // non-virtual, hides Base::vf2()

    D* vf4(){print();};      // overrides Base::vf4() and has covariant return type
//  A* vf5(){print();};      // Error: A is incomplete type
};
 
int main()
{
    Derived d;
    Base& br = d;
    Derived& dr = d;
 
    std::cout<<typeid(br.vf4()).name()<<std::endl; // calls Derived::vf4() and converts the result to B*
    std::cout<<typeid(dr.vf4()).name()<<std::endl; // calls Derived::vf4() and does not convert the result to B*

    B* p = br.vf4(); // calls Derived::vf4() and converts the result to B*
    D* q = dr.vf4(); // calls Derived::vf4() and does not convert the result to B*
}

Here is the output:

P1B
P1D
virtual D* Derived::vf4()
virtual D* Derived::vf4()

Upvotes: 0

Views: 64

Answers (3)

John
John

Reputation: 3494

To help the readers of this post.

Here is demo code snippet:

#include <iostream>
#include <string_view>

class Base
{
public:
    // This version of getThis() returns a pointer to a Base class
    virtual Base* getThis() { std::cout << "called Base::getThis()\n"; return this; }
    void printType() { std::cout << "returned a Base\n"; }
};

class Derived : public Base
{
public:
    // Normally override functions have to return objects of the same type as the base function
    // However, because Derived is derived from Base, it's okay to return Derived* instead of Base*
    Derived* getThis() override { std::cout << "called Derived::getThis()\n";  return this; }
    void printType() { std::cout << "returned a Derived\n"; }
};

int main()
{
    Derived d{};
    Base* b{ &d };
    d.getThis()->printType(); // calls Derived::getThis(), returns a Derived*, calls Derived::printType
    b->getThis()->printType(); // calls Derived::getThis(), returns a Base*, calls Base::printType

    return 0;
}

Here is the output of the code snippet above:

called Derived::getThis()
returned a Derived
called Derived::getThis()
returned a Base

One interesting note about covariant return types: C++ can’t dynamically select types, so you’ll always get the type that matches the actual version of the function being called.

In the above example, we first call d.getThis(). Since d is a Derived, this calls Derived::getThis(), which returns a Derived*. This Derived* is then used to call non-virtual function Derived::printType().

Now the interesting case. We then call b->getThis(). Variable b is a Base pointer to a Derived object. Base::getThis() is a virtual function, so this calls Derived::getThis(). Although Derived::getThis() returns a Derived*, because Base version of the function returns a Base*, the returned Derived* is upcast to a Base*. Because Base::printType() is non-virtual, Base::printType() is called.

In other words, in the above example, you only get a Derived* if you call getThis() with an object that is typed as a Derived object in the first place.

Note that if printType() were virtual instead of non-virtual, the result of b->getThis() (an object of type Base*) would have undergone virtual function resolution, and Derived::printType() would have been called.

Upvotes: 0

JaMiT
JaMiT

Reputation: 16873

the return type of the overridden function that was called.

Note the word choice. The function that was called, not the function that was executed. Your examples focus on executing the same function. The function that was called depends on how you place the call (compile time), not how the call is answered (run time).

For a reference ref of type A&, the expression ref.foo() is call to A::foo(). After the call is made, polymorphism may kick in and redirect execution to a different function, but this does not change what was called. You called A::foo(), but maybe it was B::foo() that answered the call. Virtual functions are tricky like that, covering for each other.

Do you mind? Not really. You are interested in results, not in who delivers them. As long as the function answering the call honors the contract made by A::foo(), all is fine. They can trade shifts if they wish.

In this story, the contract includes the type of the returned value. This cannot be messed with, or else the call stack gets corrupted. Unfortunately, there might be a covariant return type in play. Fortunately, B::foo() knows that if it is covering for A::foo(), it has to adapt to the current contract and throw in an implicit conversion gratis when it returns. It just goes without saying.

Still, B considers the conversion a downgrade. It's fine for the riffraff using A references. Let them have their inferior return value; the good stuff is reserved for those discriminating few who insist upon B references. These fine folks will call B::foo(), and the downgrade/conversion is skipped for them. Classy.

Upvotes: 1

John
John

Reputation: 3494

Here is the answer of @. Help it helps for other readers. The quote only states what one would expect anyways:

struct Derived : public Base
{
    void vf1(){print();};    // virtual, overrides Base::vf1()
    void vf2(int){print();}; // non-virtual, hides Base::vf2()

    D* vf4(){print();};      // overrides Base::vf4() and has covariant return type
//  A* vf5(){print();};      // Error: A is incomplete type
};

Derived::vf4 returns a D*. Anything else would be a surprise, because D::vf4 is declared to return a D*.

If you consider that Base::vf4 returns a B* and then one might wonder whether this call

D* q = dr.vf4(); // calls Derived::vf4() and does not convert the result to B* Returns a B* because thats what Base::vf4 returns. Remember that return type of overloaded functions must match. With polymorphism the caller typically does not know or care what is the actual function being called. They only rely on the interface as declared in Base so they assume its a B*. Though, covariant return types are special. The returns type can differ. Because in the example B is a private base of D, a polymorphic, relying on the Base interface, B* q = dr.vf4(); would even fail to compile.

Upvotes: 0

Related Questions