Tags

, ,

Virtual functions are a great feature, but they have a rather severe limitation. They can only be implemented by extending the definition of all classes involved. A lot of operations on classes don’t need to be members, and sticking them in the class, just to use virtual dispatching, leads to interface pollution. I’m proposing here an out-of-class virtual dispatching syntax.

The basic problem

In my Leaf compiler code I have an object of type expression, which could one of several sub-types. I wish to do some processing on this object. Some of the sub-types, not necessarily all, require special processing. Essentially I want to pass my object to one of these functions, depending on its type:

1
2
3
void process( expression )
void process( expression_binary )
void process( expression_function )

There are two basic approaches to this dispatch: visitors and switches.

A visitor pattern

If we’re willing to retrofit the types involved we can create a visitor pattern. Add a accept function to each type in the class hierarchy and then build a visitor object.

1
2
3
4
5
6
7
class process_visitor : expression_visitor {
    void visit( expression )
    void visit( expression_binary )
    void visit( expression_function )
}

object.accept( process_visitor )

The problem here is that I haven’t avoided modifying the origin type. I need to add an accept function to the base and every derived class. If I forget to add the function to a newly derived class then it breaks.

It also requires some kind of expression_visitor interface to be defined. What exactly should that be? Usually it will contain a definition for all the subtypes of expression. But I don’t always want to override the processing for all expressions.

Due to its limited functionality and a lot of boilerplate code I tend not to use the pattern often.

A big old switch

The other common approach is a switch statement:

1
2
3
4
5
switch( type_of(object) ) {
    case t_binary: ...
    case t_function: ...
    default: ...
}

This requires some way to identify the types, either natively in the language, or with a custom type id. Additionally, the switch statements I’ve seen don’t deal well with a value hierarchy. We can’t easily select a sub-tree of the type, instead we’re forced to itemize every leaf type. For this reason a series of if statements is often used instead.

1
2
3
4
5
6
if( object instanceof expression_binary ) ) {
    ...
} else if( object instanceof expression_function ) ) {
    ...
} else {
}

This is a lot of repetitive coding, and it’s possible to get the order of the if statements wrong (like checking for a base type prior to a derived type).

Standalone virtual functions

Both approaches are just workarounds for what I really want: standalone virtual functions. I just want to express a series of functions with a common name that accept different types:

1
2
3
void process( virtual expression & ) = 0;
void process( virtual expression_binary & b );
void process( virtual expression_funccall & f );

The virtual bit expresses that dispatch to this overload is done based on the runtime type of the first parameter. I call this just as any other overloaded function:

1
process( object );

At run-time the dispatch will be done to the function best matching the type: following the overload rules as though it were a static dispatch with the most derived type.

This has several advantages over the other approaches:

  • it is very compact without repetitive boilerplate code
  • the original types are left unmodified
  • it can dispatch over a subset of all the derived types
  • it doesn’t require a user-defined type identifier
  • it can deal with dispatching to mid-level types, not just leaf types

This feature would be a great addition to any OOP language with virtual functions. It’s a simple way to extend classes without touching the classes themselves.

Advertisements