Home > Net >  How to understand the type returned by the final overrider is implicitly converted to the return typ
How to understand the type returned by the final overrider is implicitly converted to the return typ

Time:06-18

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()

CodePudding user response:

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.

CodePudding user response:

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.

  • Related