2

I would like to implement the following thing in C++:

I would like to have a bunch of child classes of a single class with the ability to call a function that takes a pair of objects of any of these types. There is supposed to be a generic implementation that is called for mixed types or the base type and specialised implementations which get called if two objects of the same derived type are used as arguments.

As far as I know, this is a classic application of double dispatch. However, I have the following constraint:

It must be possible to derive new classes from the existing ones and add new pair-functions for these new classes without changing existing classes, for instance in an external library..

The approach I proposed in my last question is faulty, and the solution proposed there only works for types that are known at the time when the base class is written.

Any suggestion on how to implement this? Is that even possible?

Update: Code says more than a thousand words. The following approach works:

#include <iostream>

class B;

class A
{
public:
  virtual void PostCompose(A* other)
    {
      other->PreCompose(this);
    }
  virtual void PreCompose(A* other)
    {
      std::cout << "Precomposing with an A object" << std::endl;
    }
  virtual void PreCompose(B* other);
};

class B : public A
{
public:
  using A::PreCompose;
  virtual void PostCompose(A* other)
    {
      other->PreCompose(this);
    }
  virtual void PostCompose(B* other)
    {
      other->PreCompose(this);
    }
  virtual void PreCompose(B* other)
    {
      std::cout << "Precomposing with a B object" << std::endl;
    }
};

void A::PreCompose(B* other)
  {
    PreCompose((A*)other);
  }

int main()
{
  B b;
  A* p = &b;
  p->PostCompose(p); // -> "Precomposing with a B object"
}

but it requires knowledge of B when implementing A. Is there a better way?

Community
  • 1
  • 1
lytenyn
  • 819
  • 5
  • 21

1 Answers1

1

Since the derived classes only need to detect if the parameter type matches the object type, you can just use a straightforward check.

virtual void foo( base *argument_base ) {
    if ( derived *argument = dynamic_cast< derived * >( argument_base ) ) {
        argument->something = pair_match_foo;
    } else {
        base_class::foo( argument_base );
    }
}
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • The problem with the wikipedia example is that you have to know about all derived classes when implementing the base class which I cannot do. I am looking for a method that allows me to add derived classes later on without changing the base class. – lytenyn Apr 14 '11 at 12:54
  • To make that more precise, I want double dispatch in the case where I only have one class hierarchy, not two. There is no 'target class', since the class itself is its target. This is no problem, but requires me to know the whole class hierarchy when writing the bas class, which is not what I want. – lytenyn Apr 14 '11 at 13:02
  • @lytenyn: Sorry, I was a little rusty on that. I'm also not clear if `compose` is intended to be the operation or the dispatching mechanism itself… anyway see update. – Potatoswatter Apr 14 '11 at 13:17
  • @Potatoswatter: thank you, this would neatly solve the problem. However, I am reluctant to use `dynamic_cast`, but I cannot put my finger on why. It is certainly slow, but this is not a time-critical operation anyway. Can you think of something not involving `dynamic_cast`? Or tell me that my uneasy feeling is not justified? – lytenyn Apr 14 '11 at 13:26
  • @lytenyn: `dynamic_cast` involves walking up the class hierarchy to determine if `derived` is the actual type of, or among the bases, of the given object. If it's the actual type, then `dynamic_cast` succeeds after just one comparison. Otherwise, it fails after two comparisons, because there is only one base. If the hierarchy is much more complicated, then the optimization isn't difficult, but this should be plenty fast. – Potatoswatter Apr 14 '11 at 13:38
  • @Potatoswatter: Ok, thank you. Somehow I still feel that this is not an elegant solution, but again, I cannot really say why. Somehow I remember saying that, if you need `dynamic_cast`, your class design is probably wrong, but I cannot think of a better way to solve my problem. – lytenyn Apr 14 '11 at 13:45
  • 1
    @lytenyn: I think this is one use that doesn't count :v) . If you wanted, you could replace it with an additional virtual function to return an `enum` value identifying the sub-hierarchy of the class, or simply a member variable with the same information. I think that alternative is less elegant. Many students have been taught to use `dynamic_cast` where `static_cast` would be appropriate. That is a big red flag, and I can imagine a backlash against the operator itself. – Potatoswatter Apr 14 '11 at 13:59