Get derived type via base class virtual function
Asked Answered
G

5

9

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!

Glossary answered 21/10, 2013 at 18:20 Comment(3)
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.Talipes
@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.Saddlery
@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.Talipes
G
10

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

Gargan answered 21/10, 2013 at 19:27 Comment(0)
E
3

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.

Epigrammatize answered 21/10, 2013 at 18:28 Comment(1)
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.Talipes
S
1

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
}
Samarium answered 21/10, 2013 at 18:56 Comment(8)
'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?Talipes
@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.Samarium
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).Talipes
@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.Samarium
@g-makulik Besides, the only thing the OP explicitly asked for was why he got the compilation error.Samarium
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.Talipes
'Besides, the only thing the OP explicitly asked for ...' Yes, but you could clearly see his intend ;) ...Talipes
@g-makulik Perhaps, but I am declared explicit :)Samarium
P
1

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&
Phosphoprotein answered 21/10, 2013 at 19:2 Comment(3)
So c++ covariant return types are pretty useless when we're talking about the deriving class's type itself, aren't they?Talipes
@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... ?Phosphoprotein
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.Deflect
T
-2

I found a simple solution, but if is possible, I would the masters to evaluate:

class base{ 
    type = 1;
    virtual int getType() final {
        return type;
    }
}

class derived1 : public base {
    derived1(){
        type = 2;
    }
}

This way, you can call the method 'int getType()' of any of derived classes. As the type is set on the constructor, there is no risk of misbehaviour. To enhance the usability, i've created a predefined 'types'.

I'm using, but I don't know if is MacGyvery!

Turbosupercharger answered 17/8, 2015 at 1:38 Comment(3)
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.Andalusia
Also the solution above can become your headache :)Angeli
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.Deflect

© 2022 - 2024 — McMap. All rights reserved.