Why doesn't C++ allow you to request a pointer to the most derived class?
Asked Answered
L

10

5

(This question should probably be answered with a reference to Stroustrup.)

It seems extremely useful to be able to request a pointer to the most derived class, as in the following:

class Base { ... };
class DerivedA { ... };
class DerivedB { ... };
class Processor
{
  public:
  void Do(Base* b) {...}
  void Do(DerivedA* d) {...}
  void Do(DerivedB* d) {...}
};

list<Base*> things;
Processor p;
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    p.Do(CAST_TO_MOST_DERIVED_CLASS(*i));
}

But this mechanism isn't provided in c++. Why?

Update, Motivating Example:

Suppose instead of having Base and Derived and Processor, you have:

class Fruit
class Apple : public Fruit
class Orange: public Fruit

class Eater
{
   void Eat(Fruit* f)  { ... }
   void Eat(Apple* f)  { Wash(f); ... }
   void Eat(Orange* f) { Peel(f); ... }
};

Eater me;
for each Fruit* f in Fruits
    me.Eat(f);

But this is tricky to do in C++, requiring creative solutions like the visitor pattern. The question, then, is: Why is this tricky to do in C++, when something like "CAST_TO_MOST_DERIVED" would make it much simpler?

Update: Wikipedia Knows All

I think Pontus Gagge has a good answer. Add to it this bit from the Wikipedia entry on Multiple Dispatch:

"Stroustrup mentions that he liked the concept of Multi-methods in The Design and Evolution of C++ and considered implementing it in C++ but claims to have been unable to find an efficient sample implementation (comparable to virtual functions) and resolve some possible type ambiguity problems. He goes on to state that although the feature would still be nice to have, that it can be approximately implemented using double dispatch or a type based lookup table as outlined in the C/C++ example above so is a low priority feature for future language revisions."

For background, you can read a little summary about Multi-Methods, which would be better than a call like the one I mention, because they'd just work.

Limber answered 16/6, 2010 at 14:15 Comment(2)
I've seen some crack designs before, but... oh man... Base::Do(), DerivedA::Do(), DerivedB::Do() and the problem goes away.Threatt
But sometimes that's not an option, or it's not the best choice semantically. See the response to DeadMG, below.Limber
F
4

What you are suggesting would be equivalent to a switch on the runtime type, calling one of the overloaded functions. As others have indicated, you should work with your inheritance hierarchy, and not against it: use virtuals in your class hierarchy instead of dispatching outside it.

That said, something like this could be useful for double dispatch, especially if you also have a hierarchy of Processors. But how would the compiler implement it?

First, you'd have to extract what you call 'the most overloaded type' at runtime. It can be done, but how would you deal with e.g. multiple inheritance and templates? Every feature in a language must interact well with other features -- and C++ has a great number of features!

Second, for your code example to work, you'd have to get the correct static overload based on the runtime type (which C++ does not allow as it is designed). Would you like this to follow the compile time lookup rules, especially with multiple parameters? Would you like this runtime dispatch to consider also the runtime type of your Processor hierarchy, and what overloads they have added? How much logic would you like the compiler to add automatically into your runtime dispatcher? How would you deal with invalid runtime types? Would users of the feature be aware of the cost and complexity of what looks like a simple cast and function call?

In all, I´d say the feature would be complex to implement, prone to errors both in implementation and usage, and useful only in rare cases.

Ferrante answered 16/6, 2010 at 14:46 Comment(6)
You don't need this for double-dispatch. You provide an overload of Processor for each, as he's done, then you call the virtual call in Base, which will pass this to Processor, thus calling the correct overload, which can then be dispatched up the Processor heirarchy.Spoiler
@DeadMG note that he doesn't say you /need/ it for double dispatch, he said it would be /useful/ for double dispatch (and multiple dispatch for that matter).Limber
@Pontus Aha! An answer to the actual question! Let me read a bit more before I accept ...Limber
@Matthew Lowe: Sure. But then, run-time function generation would be useful for dynamic behaviour. Humans would be useful for scientific testing. That doesn't mean that using them is a good idea or the right idea.Spoiler
@DeadMG Agreed. Just because it could be done doesn't mean you should do it.Limber
@Matthew: Yes. And what you want in your OP shouldn't be done.Spoiler
P
8

Probably because that's what virtual functions do for you instead. The implementation of the virtual function that is nearest the most-derived class will be called when you invoke it through a base class pointer or reference.

Plaintive answered 16/6, 2010 at 14:19 Comment(2)
Actually, virtual functions don't do that for you, because the virtual function has to be in the Base/Derived class, which is not the same as the function /not/ being in the Base/Derived class. I'll edit with a motivating example.Limber
@Matthew Lowe: Your question was "But this mechanism isn't provided in c++. Why?" where "this" was the lack of a cast to the most-derived type. Virtual functions allow you to achieve what you want through things like the visitor pattern. Obviously you have to design things differently to the way you were hoping but they can solve the problem for you. BTW, I answered the question you asked, in contrast to the implication in your comment on @Ponuts Gagge's answer.Plaintive
C
8

Firstly, C++ does allow you to request a pointer to a most derived class in numerical terms (i.e. just the numerical value of the address). This is what dynamic_cast to void* does.

Secondly, there's no way to obtain a pointer to the most derived class in therms of exact type of the most derived class. In C++ casts work with static types, and static type is a compile-time concept. Type-based function overloading is also a compile-time process. The exact most derived type is not known at compile-time in your case, which is why cannot cast to it and can't resolve overloading on it. The request to have such a cast makes no sense in the realm of C++ language.

What you are trying to implement (if I understood your intent correctly), is implemented by completely different means, not by a cast. Read about double dispatch, for one example.

Counterproductive answered 16/6, 2010 at 14:36 Comment(4)
dynamic_cast with void* ? really? i was under the impression that you couldn't do that. of course, it'd be possible after you changed the pointer type to whatever else, and then dynamic cast would work..Foreandaft
@ianmac45: No. By definition, dynamic_cast<void *>(p) (where p is a pointer to a polymorphic type) evaluates to a pointer to the most derived object. Of course, it is only so numerically. The result type is still just void *.Counterproductive
Note that my question isn't "can you do this in C++" but "why can't you do this in C++". I think Pontus Gagge does a pretty good job of answering this question.Limber
@Matthew Lowe: The second paragraph of my answer ("Secondly...") is exactly the answer to the "why" question.Counterproductive
K
6

Because the type of i is not determinable at compile time. Therefore the compiler would not know which function call to generate. C++ only supports one method of dynamic dispatch that is the virtual function mechanism.

Kulsrud answered 16/6, 2010 at 14:22 Comment(3)
au contraire! I could use several calls to dynamic_cast and actually get the job done. the question is, "why can't I just ask for the most derived class instead?"Limber
You could dynamic cast your way around but the resulting function call would not be dynamic. Do( base*) would always call the function that took the base parameter, etc. If cast to most derived worked which non dynamic function call should be generated? The only one that would work for all classes in the tree would be the base class version. If you want to make a dynamic function call you only have one tool capable of doing it.Kulsrud
I don't think I've correctly communicated my intention. What I mean by "cast to most derived class" is, if the object is actually a DerivedB, I want a CAST_TO_MOST_DERIVED_CLASS(&object) to return a DerivedB*. If it is a DerivedC, I want a DerivedC*.Limber
S
4

It's called using a virtual function call. Pass the processor* into DerivedA/B's virtual method. Not the other way around.

There is no mechanism provided because it's totally unnecessary and redundant.

I swear, I fielded this exact question about a day or two ago.

Spoiler answered 16/6, 2010 at 14:17 Comment(4)
No, it's not unnecessary and redundant. It would be useful in all sorts of situations requiring multiple dispatch. (So, for example, suppose that you can't modify DerivedA/B. Or that semantically, it doesn't make sense to put Do() in the Base/Derived. I will provide a more concrete example if the problem isn't clear.)Limber
Any function where the behaviour needs to alter if the class is not Base, goes in the virtual hierarchy. If your semantics are different to that, then it's time to fix your semantics. If you can't alter Derived, then complain to whoever can, because they screwed you.Spoiler
Well, suppose that you have a Fruit class, with derived classes Apple and Orange. And you're trying to implement a polymorphic Eat() function that does the appropriate things for an apple (washing it) or an orange (peeling it). It doesn't make sense to put Eat() in the Fruit class, because a Fruit shouldn't know how to eat itself. Using dynamic_cast or a visitor pattern for double dispatch solves this problem, but so would the CAST_TO_MOST_DERIVED that I mention.Limber
It does make sense to put Eat in the Fruit class, because Eat depends on which Fruit you have.Spoiler
F
4

What you are suggesting would be equivalent to a switch on the runtime type, calling one of the overloaded functions. As others have indicated, you should work with your inheritance hierarchy, and not against it: use virtuals in your class hierarchy instead of dispatching outside it.

That said, something like this could be useful for double dispatch, especially if you also have a hierarchy of Processors. But how would the compiler implement it?

First, you'd have to extract what you call 'the most overloaded type' at runtime. It can be done, but how would you deal with e.g. multiple inheritance and templates? Every feature in a language must interact well with other features -- and C++ has a great number of features!

Second, for your code example to work, you'd have to get the correct static overload based on the runtime type (which C++ does not allow as it is designed). Would you like this to follow the compile time lookup rules, especially with multiple parameters? Would you like this runtime dispatch to consider also the runtime type of your Processor hierarchy, and what overloads they have added? How much logic would you like the compiler to add automatically into your runtime dispatcher? How would you deal with invalid runtime types? Would users of the feature be aware of the cost and complexity of what looks like a simple cast and function call?

In all, I´d say the feature would be complex to implement, prone to errors both in implementation and usage, and useful only in rare cases.

Ferrante answered 16/6, 2010 at 14:46 Comment(6)
You don't need this for double-dispatch. You provide an overload of Processor for each, as he's done, then you call the virtual call in Base, which will pass this to Processor, thus calling the correct overload, which can then be dispatched up the Processor heirarchy.Spoiler
@DeadMG note that he doesn't say you /need/ it for double dispatch, he said it would be /useful/ for double dispatch (and multiple dispatch for that matter).Limber
@Pontus Aha! An answer to the actual question! Let me read a bit more before I accept ...Limber
@Matthew Lowe: Sure. But then, run-time function generation would be useful for dynamic behaviour. Humans would be useful for scientific testing. That doesn't mean that using them is a good idea or the right idea.Spoiler
@DeadMG Agreed. Just because it could be done doesn't mean you should do it.Limber
@Matthew: Yes. And what you want in your OP shouldn't be done.Spoiler
G
2

In C++ overload resolution happens at compile time. Your example would require determining the real type of *i at runtime. For it to be done at runtime would require a runtime type check, and because C++ is a performance oriented language it purposefully avoids this cost. If you really wanted to do this (and I'd be curious to see a more realistic example) you could dynamic_cast to the most derived class, then if that fails to the second most derived class, and so on, but this requires knowing the class hierarchy up front. And knowing the full hierarchy up front maybe impossible -- if the DerivedB class is in a public header, it's possible another library uses it and has made an even more derived class.

Georgiageorgian answered 16/6, 2010 at 14:48 Comment(0)
D
2

You're looking for double dispatch. It can be done in C++, as shown at that link, but it's not pretty, and it basically involves using two virtual functions calling each other. If you can't modify some of the objects in your inheritance tree, you may not be able to use this technique either.

Declension answered 16/6, 2010 at 14:50 Comment(1)
Actually, I'm not looking for double dispatch, because I'm aware of it already. Instead, I'm wanting to know why this language feature isn't available.Limber
O
1

This is not possible in C++, but what you want to achieve is easily doable using the Visitor design pattern:

class Base
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedA
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedB
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class BaseVisitor
{   
    virtual void visit(Base* b) = 0;
    virtual void visit(DerivedA* d) = 0;
    virtual void visit(DerivedB* d) = 0;
};

class Processor : public BaseVisitor
{
    virtual void visit(Base* b) { ... }
    virtual void visit(DerivedA* d) { ... }
    virtual void visit(DerivedB* d) { ... }
};

list<Base*> things;
Processor p;
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    (*i)->visit(p);
}
Oscillograph answered 16/6, 2010 at 14:57 Comment(0)
B
1

Why doesn't C++ have it? Perhaps the creators never thought about it. Or perhaps they didn't consider it suitable or useful enough. Or perhaps there were problems with actually trying to do it in this language.

On that last possibility, here's a thought experiment:

Lets say the this feature exists so that the compiler will write code that examines the dynamic type pointed to and calls the appropriate overload. Now lets also say a separate portion of the code has class DerivedC : Base {...};. And say that the corresponding Processor::Do overload is not added.

Given all of that, what should the program do when it tries to choose the appropriate overload? This discrepancy cannot be caught at compile-time. Should it try to climb the class hierarchy to find a function that matches a base class? Should it throw a special exception? Should it just crash? Is there some other possibility? Is there actually any reasonable choice that the compiler could make on its own without knowing the intention of your code and class hierarchy?

Yes, writing such functionality yourself would be susceptible to the same problem, but there the programmer has total control to choose the behavior, not the compiler.

Behaviorism answered 16/6, 2010 at 23:36 Comment(0)
B
0

C++ interprets data in the context of the associated type. When you store an instance of DerivedA* or DerivedB* in a list, that associate type must necessarily be Base*. This means that the compiler itself can no longer determine that those are pointers to one of the subclasses rather than the base class. While in theory you could cast to a LESS derived class by looking at the associated type's inheritance, the information needed to do what you want simply isn't available at compile-time.

Burnoose answered 16/6, 2010 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.