0
votes

I'm trying to create an overloaded function that will be called with the dynamic type of an object. I try to do this without interfering with the actual class structure underneath, as I don't have direct access (i.e. I cannot add virtual methods, etc.)

As a concrete example, let's think of an AST class structure that looks somewhat like this:

class ASTNode {}; // this one is fully abstract; i.e. there's a virtual void method() = 0;

class Assignment : ASTNode {};
class Expression : ASTNode {};

class StringExpr : Expression {};
class MathExpr : Expression {};

I want to write a function act that will take an instance of ASTNode as parameter and, depending on its actual dynamic type do something different. The call will be something like this

std::shared_ptr<ASTNode> parsedAST = get_a_parsed_ASTNode(); // ... received from some parser or library
act(parsedAST);

Then, I want to act, depending on the dynamic type of the ASTNode.

void act(std::shared_ptr<MathExpr> expr)
{
  // Do something with Math expressions, e.g. evaluate their value
};

void act(std::shared_ptr<StringExpr> expr)
{
  // Do something with String  expressions, e.g. write their value to the log
};

void act(std::shared_ptr<Expression> expr)
{
  // do something with other types of expressions (e.g. Boolean expressions)
}; 

Currently though, I cannot call since they dynamic type will be maybe not the ``most concrete type''. Instead, I have to manually create a dispatcher manually as follows, but the method is a bit silly in my opinion, since it does literally nothing else but dispatch.

void act(std::shared_ptr<ASTNode> node_ptr)
{
  if(std::shared_ptr<MathExpr> derived_ptr = std::dynamic_pointer_cast<MathExpr>(node_ptr))
  {
    act(derived_ptr);
  }
  else if(std::shared_ptr<StringExpr> derived_ptr = std::dynamic_pointer_cast<StringExpr>(node_ptr))
  {
    act(derived_ptr);
  }
  else if(std::shared_ptr<Expression> derived_ptr = std::dynamic_pointer_cast<Expression>(node_ptr))
  {
     // do something with generic expressions. Make sure that this is AFTER the more concrete if casts
  }
  else if( ... )  // more of this
  {
     
  }
  // more else if
  else
  {
     // default action or raise invalid argument exception or so...
  }
};

This is especially annoying & error-prone since my class hierarchy has many (> 20) different concrete classes that can be instantiated. Also, I have various act-functions, and when I refactor things (e.g. add an act for an additional type), I have to make sure to pay attention to the correct order of if(dynamic_pointer_cast) within the dispatcher. Also it's not that stable, since a change in the underlying class hierarchy will require me to change every dispatcher directly, rather than just the specific act functions.

Is there a better / smarter solution? Evidently I'd appreciate "native" solutions, but I'm willing to consider libraries too.

1
Is it only the base class that is not under your control, or also some of the derived classes? If you have control over all the derived classes, a CRTP approach would be possible. Otherwise you need to have the types and order of the types somewhere in your code. Making a template that generates your dispatch function from a list could be a somewhat more maintainable option.super
unfortunately it's the entire class hierarchy that is out of my control. CRTP seems interesting, but I believe it doesn't fit well, unless I somehow "shadow" the entire class hierarchy by creating a double inheritance structure where every "shadow" class inherits from 1. the original class, 2. it's shadow super. Then I could add my logic to the shadow classes. However, that still leaves me with an entire shadow class hierarchy that I need to maintain.stklik

1 Answers

0
votes

Never encountered such problem myself, but can think of the following solution.

Create you hierarchy that mimics original hierarchy, has virtual act, the base has base pointer, and each cast it to the corresponding derived pointer.

Now, to create the needed wrapper, you don't need properly ordered dynamic_cast, dispach on typeid string. So your dispatch is a map from string to wrapper factory.

Sure you need RTTI for typeid string, but you would need it for dynamic_cast as well.