Get derived type via base class virtual function

32,115

Solution 1

C++ covariant return types support will only work, as long you already know the derived type. To downcast a base class to a possibly derived class, simply use dynamic_cast<derived>(base_ref) to determine if base_ref matches the actual derived type:

int main () {
    base* pd = new derived();
    derived& x = dynamic_cast<derived&>(*pd); // Will throw an exception if pd 
                                          // isn't a 'derived'
    x.fn();
    return 0;
}

Or alternatively:

int main () {
    base* pd = new derived();
    derived* x = dynamic_cast<derived*>(pd); // Will return nullptr if pd isn't
                                         // a 'derived'
    if(x) {
        x->fn();
    }
    else {
        // dynamic_cast<derived*> failed ...
    }
    return 0;
}

supports covariant return types for derived classes, but as the other answers describe you cannot get it via calling the base class (pd->get_this()) here.

You might also consider static polymorphism to check type compliance at compile time, if you can't use RTTI, exception handling or want tight type binding (without vtable overhead).

Solution 2

The static type of pd is base *. Thus, when the compiler looks for the member function get_this(), it finds only base::get_this(). The return type of base::get_this() is base&, which is not convertible to derived&. Hence the error.

Solution 3

I would like to add to Novelocrat's answer by referring you to section 10.3, paragraph 8 of the working draft C++ standard (click here) which explains in which case the returned pointer's static type is Derived* as opposed to Base*. Basically, if you would have called get_this() through a pointer to the dervied class then you would have gotten the right type with no compiler error.

Here is a quote from the standard along with an example (also from the standard):

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). [Example:

class B { };
class D : private B { friend class Derived; };
struct Base {
    virtual void vf1();
    virtual void vf2();
    virtual void vf3();
    virtual B* vf4();
    virtual B* vf5();
    void f();
};

struct No_good : public Base {
    D* vf4(); // error: B (base class of D) inaccessible
};

class A;
struct Derived : public Base {
    void vf1(); // virtual and overrides Base::vf1()
    void vf2(int); // not virtual, hides Base::vf2()
    char vf3(); // error: invalid difference in return type only
    D* vf4(); // OK: returns pointer to derived class
    A* vf5(); // error: returns pointer to incomplete class
    void f();
};

void g() {
    Derived d;
    Base* bp = &d; // standard conversion:
    // Derived* to Base*
    bp->vf1(); // calls Derived::vf1()
    bp->vf2(); // calls Base::vf2()
    bp->f(); // calls Base::f() (not virtual)
    B* p = bp->vf4(); // calls Derived::pf() and converts the
    // result to B*
    Derived* dp = &d;
    D* q = dp->vf4(); // calls Derived::pf() and does not
    // convert the result to B*
    dp->vf2(); // ill-formed: argument mismatch
}

Solution 4

C++ supports covariant return type. What it means is that when you call get_this() on a derived object through a base pointer it is the implementation of derived that is going to be called.

However this does not mean that calling base::get_this will give you a derived&. The return type of base::get_this is base&. if you want to get a derived object you will have to call get_this through a derived pointer (or downcast your base& to a derived&). Note that this is how return type covariance work in Java, C++, D...

base* pbase = new base();
base* pderived = new derived();
derived* pderived2 = new derived();

base& a = pbase->get_this();        // call implementation in base, return base&
base& b = pderived->get_this();     // call implementation in derived, return base&
derived& c = pderived2->get_this(); // call implementation in derived, return derived&
Share:
32,115

Related videos on Youtube

linuxfever
Author by

linuxfever

Updated on August 17, 2020

Comments

  • linuxfever
    linuxfever almost 4 years

    I am trying to get the derived type of an object via a base class virtual function. I have written this, which does not compile:

    struct base {
      virtual base& get_this() {
        return *this;
      }
    };
    
    struct derived : base {
      virtual derived& get_this() override {
        return *this;
      }
    
      void fn();
    };
    
    
    int main () {
      base* pd = new derived();
      derived& x = pd->get_this(); /*ERROR*/
      x.fn();
      return 0;
    }
    

    ... giving me an error that: I cannot initialize a derived& from a base. Since get_this is virtual, why does pd->get_this() return a base& instead of a derived&? Thanks in advance!

    EDIT:

    Thanks everyone for their useful answers and apologies for my late reply. I should have specified in the original post that I am also interested in a solution to my problem rather than just figuring out why the above does not compile. My main problem is that fn is unique to the derived class and cannot be called via the base class. Using casts sure solves the problem but I hate writing code with if else constructs just to get the right type (also Scott Meyers advise against casts :)) . The answers seem to indicate that casts are the way to go, which in a way is at least reassuring that I am not neglecting a more 'elegant' solution to my problem. Thanks again!

    • πάντα ῥεῖ
      πάντα ῥεῖ over 10 years
      Deleted my answer, but I still believe the OP will be better off using a simple dynamic_cast which will be a viable solution for the use case described. No one else mentioned this in the answers, and the way covariance is supported in C++ doesn't solve the OP's primary problem.
    • Konrad Rudolph
      Konrad Rudolph over 10 years
      @g-makulik Well, yeah, you could (should) have just removed or modified the contentious sentence – the rest is spot-on and a good answer. Now you’ve thrown out the child with the bath water.
    • πάντα ῥεῖ
      πάντα ῥεῖ over 10 years
      @KonradRudolph Gave another one (leaving out the picky words). At least you're right: downvote rate was less than reputation gain at that point. But I'm getting nervous on downvotes, and experience shows they'll never be retracted on appropriately edited answers.
  • πάντα ῥεῖ
    πάντα ῥεῖ over 10 years
    So c++ covariant return types are pretty useless when we're talking about the deriving class's type itself, aren't they?
  • πάντα ῥεῖ
    πάντα ῥεῖ over 10 years
    'if you would have called get_this() through a pointer to the dervied class then you would have gotten the right type with no compiler error' So c++ covariant return types are pretty useless when we're talking about the deriving class's type itself, aren't they?
  • πάντα ῥεῖ
    πάντα ῥεῖ over 10 years
    I agree that this perfectly answers why the OP gets an error on compilation, but doesn't show up a viable alternative to get rid of the error.
  • Admin
    Admin over 10 years
    @g-makulik They do allow you to get the right type when calling a method from the derived type. Otherwise you would have to cast.
  • πάντα ῥεῖ
    πάντα ῥεῖ over 10 years
    The crucial point for the OP's use case is, that you'll need to know the derived type to get the correct covariant return type of a function, thus it doesn't help much for downcasting (which is obviously what the OP wants here).
  • log0
    log0 over 10 years
    @g-makulik In fact I don't know any language with covariant return type where it does not work like this :/ when you are manipulating a base reference, it could be any type behind, so how could you know what should be the type of x... ?
  • Admin
    Admin over 10 years
    @g-makulik I never claimed the purpose of my answer was to propose an alternative. I am merely backing up Novelocrat's answer with appropriate excerpts from the standard, which I think is enlightening. I didn't propose downcasting with dynamic_cast primarily because I consider it to be a suspicious solution most of the time. Depending on his needs, maybe a better solution would be to simply add an appropriate virtual function that correctly executes the right code based on the dynamic object type. I mean, that's why virtual functions were invented in the first place.
  • Admin
    Admin over 10 years
    @g-makulik Besides, the only thing the OP explicitly asked for was why he got the compilation error.
  • πάντα ῥεῖ
    πάντα ῥεῖ over 10 years
    I also don't want to propagate down casting using dynamic_cast. That's why I've mentioned static polymorphism, which will be the better design choice in most cases IMHO. But it takes more efforts, to do it right.
  • πάντα ῥεῖ
    πάντα ῥεῖ over 10 years
    'Besides, the only thing the OP explicitly asked for ...' Yes, but you could clearly see his intend ;) ...
  • Admin
    Admin over 10 years
    @g-makulik Perhaps, but I am declared explicit :)
  • RamblingMad
    RamblingMad almost 9 years
    All of those methods are private and type is missing a type-specifier. You couldn't even instantiate an instance of derived1 in your example.
  • Alex
    Alex over 7 years
    Also the solution above can become your headache :)
  • John P
    John P over 6 years
    This appears to be 'duck typing' based on 'magic numbers', or a manually/exhaustively-'enumerated type' minus uniqueness and the centralized index. I am a 'master' of nothing if not predicting headaches; I have to second @Alexander on that one.
  • John P
    John P over 6 years
    I would add that 'derivation' is not the same as 'divination' - you define derivatives of base classes, typically extending and refining each constructively. This is derivation, but derivation per the definition you would consider 'useful' is closer to the inverse. This is generally the price you pay for abstraction.