Stroustrup's Design and Evolution of C++ suggests a syntax
for multimethods in C++, which this proposal approximately follows.

A function prototype is a multimethod function if one or more of its
parameters are qualified with the keyword virtual.
Implementations of the multimethod have these parameters qualified with a
static.

The parameters that correspond to the virtual parameters in the virtual
function prototype must be derived from the original types, and have the same
modifiers (const/volatile). They must also derive
unambiguously from the original types - for example a cast from the base type
to the derived type must not give an `ambiguous base class' error.

Virtual function declarations shall occur before multimethod
implementation function definitions/declarations. Multimethod implementation
functions have the same name as the multimethod function but are not directly
callable, e.g. they are renamed internally by the compiler.

Here is the classic multimethods example, an overlap()
function that takes references to a shape base class. Detecting
whether two shapes overlap requires different code for each combination of
shapes:

The overlap( virtual shape& a, virtual shape& b)
prototype is replaced by a dispatch function with prototype overlap(
shape& a, shape& b). Internally, this dispatch function uses
C++ RTTI to choose one of the available overlap() functions,
based on the dynamic types of its parameters.

It is possible that there isn't any matching multimethod implementation
function for a particular combination of dynamic types. In this case, the
generated dispatch function will throw an exception. It will also throw an
exception if there is no clear best multimethod implementation for a
particular combination of dynamic types. See below for the new exception
types that can be thrown.

A multimethod implementation is considered best only if both the following
two conditions apply:

All of the best multimethod implementation's parameter types match the
dynamic types.

Each of the best multimethod implementation's parameter types is at
least as derived as the corresponding parameter type in any other
matching multimethod implementation.

This is the same as the lookup rule used by C++ to resolve overloaded
functions at compile time, apart from compile-time C++'s use of conversion
operators and special-casing of built-in types.

Note that we cannot have duplicate multimethod implementations, so the
second condition implies that for each other matching multimethod
implementation X, the best multimethod implementation must have at least one
parameter type that is more derived than the corresponding parameter type in
X.

So if the available implementations of overlap are as in the
above code example, then:

There is also the possibility of ambiguities caused by a dynamic type
multiply-inheriting from the same class more than once (a similar error can
already occur at compile time if a static type multiply-inherits from the
same class more than once).

The basic dispatch algorithm described above involves much use of RTTI and
is linear in the number of multimethod implementations available, so it is
very slow. But dispatch speed can be significantly improved using a cache.

For example. O(LogN) dispatch speed can be easily obtained using a
std::map-style dispatch cache, mapping from arrays of
std::type_info's to function pointers.

It is also possible to get constant-time dispatch if classes are assigned
small unique integer identifiers, which could be stored in the vtable. This
allows a dispatch array of pointers to functions to be created for each
multimethod, using the small integer identifiers as indices (in effect, this
is making vtables that belong to individual multimethod dispatch functions
rather than individual classes).

To avoid whole-programme analysis at build-time, it may be best to
initialise these small integers to zero, and assign non-zero values at
runtime whenever a class is first involved in a multimethod call; this would
also avoid wasting integer space on classes that aren't involved in
multimethod calls, which in turn would reduce the sizes of the dispatch
arrays.

A dispatch function for the overlap() example using these
indices would look something like:

The code leading to the `fast dispatch' could be implemented in very few
assembler instructions (e.g. around 10 instructions on an ARM), resulting in
multimethod dispatching that is comparable in speed to existing virtual
function dispatch.

There is a new abstract std::dispatch_error class derived
from std::exception. Derived from
std::dispatch_error are two new classes,
std::dispatch_ambiguous and
std::dispatch_unmatched. How much information these classes
convey is implementation-defined.

If a multimethod has exception specifications, then implementations of the
multimethod must have exceptions specifications that are at least as
restrictive. However, multimethod implementations can ommit any
std::dispatch_error from their multimethod's exception
specification if they do not call any multimethods themselves - dispatch
exceptions are generated before a multimethod's implementation is called.

Interaction of multimethod dispatch with these exception specifications is
much the same as for any other function. For example, if a multimethod has an
exception specification that does not include
std::dispatch_error, and a multimethod call is ambiguous, then
std::unexpected() will be called.

Multimethods shall not be called before main() - i.e. static
initialisation code shall not call multimethods.

This restriction is purely to allow implementations that initialise
globals before main() is entered, to use their own global
initialisation support to register multimethods at runtime, avoiding the need
to do whole-programme analysis at build time.

Implementations that do not perform link-time analysis to collect all the
multimethod implementations in a programme, and do not initialise globals
before main() is entered, will have to use a custom techique to
ensure that multimethod implementations are registered correctly before
main() is entered.

We could allow the caller of a multimethod to mark some virtual parameters
with a static prefix, telling the multimethod dispatcher to use
static types for these parameters, rather than their dynamic types.

If all virtual parameters are marked in this way, then the dispatch can in
theory be resolved at build time. Note that this is not the same as
conventional overloading, because the dispatch will still see matching
implementations in all compilation units, not just the current compilation
unit:

bool overlaps = overlap( a, b);
// Conventional dynamic dispatch using the dynamic types
// of both `a' and `b'.
bool overlaps = overlap( static a, b);
// Do multimethod dispatch but use the static type
// of `a' rather than its dynamic type, and the dynamic
// type of `b' as before.
bool overlaps = overlap( static a, static b);
// Do multimethod dispatch but use the static types
// of both `a' and `b' rather than their dynamic types.
// This call can be fully resolved at link-time.
bool overlaps = overlap(
static static_cast< triangle&>( a),
static static_cast< square&>( b));
// Example of using casts in conjunction with a request to
// use the static type. This avoids any runtime dispatch
// when we know at something about the dynamic types at
// compile time.

This is analogous to qualifying virtual function calls, where it is
guaranteed that all matching virtual functions have seen (because all base
classes must be visible when a derived class is visible). It is different
from conventional compile-time overloading, which only considers what is
visible in the current compilation unit.

Sometimes it is useful to pass a smart pointer to a function rather than a
plain pointer or reference. For example the called function might want to
copy the smart pointer into a persistent data structure before returning.

The implicit this parameter passed to C++ member functions
doesn't allow this sort of usage without the user passing the smart pointer
explicitly:

This requires three function templates to be provided that behave
similarly to dynamic_cast, static_cast and
typeid, except that they know about particular class templates
and operate on what the class templates are handles for, rather than the
class templates themslves:

A function template template_dynamic_test that is similar
to dynamic_cast, except that it returns
true/false depending on whether the dynamic
type of the pointee means that one could cast from one instantiation of
the class template to a different instantiation of the same class
template.

A function template template_static_cast that is similar
to static_cast, except that it casts from one instantiation
of the class template to a different instantiation of the same class
template. It assumes that a similar call to
template_dynamic_test would return true.

A function template_typeid that is similar to
typeid except that it takes an instance of an instantiation
of a class template and returns the std::type_info of the
object that it refers to.

When static is used inside the declaration of a function
parameter type template, it identifies the class that that parameter should
be treated as when performing dispatch. This means that one can write a whole
set of implementations that take a smart pointer to various derived types,
and the dispatch behaviour will be the same as if plain references had been
used instead of smart pointers.

For example, to make this scheme work with Boost's
boost::shared_ptr, you would use the following definitions of
the three function templates:

By analogy with virtual functions, implementations of a multimethod that
returns a reference or pointer to a type T, could be allowed to
return a reference or pointer to anything derived from T.
However, this would only be useful if the multimethod implementations could
be called directly, which is not the case. The suggested extension to support
qualifying parameters at the point of call with the static
keyword does not change this - the function call is still in principle a call
to the dispatch function, not a specific multimethod implementation.

It has been suggested that it should be possible to make member functions
into multimethods. I don't like this idea because it adds complications for
no real gain in functionality. In particular, mixing virtual member functions
and multimethods would be plain confusing and not do anything that couldn't
be done by a straightforward multimethod.

This would require extending export to instantiate any matching
multimethod implementations in all source files whenever a particular
multimethod template was instantiated.

In contrast, conventional class templates can have virtual functions
without requiring separate compilation of templates, because one can put the
virtual function definitions inside the class template itself, ensuring that
it is instantiated whenever the class template is instantiated.

However, if export was extended to support these multimethod templates,
then it would probably be straightforward to also have the equivalent of
templated virtual member functions, of both ordinary classes and class
templates, which are not supported in C++. This is because the restrictions
imposed by vtable implementations of virtual functions don't really exist
with multimethods. For example, multimethod dispatch tables are `owned' by
the multimethod, not by a class.

This could be used to restrict the number of possible multimethod
implementations that are available for consideration at runtime, so possibly
improving dispatch speed where some type information is available at compile
time. It would require that multimethod implementations are registered with
more than one multimethod.

I suspect that this would be a little-used feature, which would add
unnecessary complexity to the proposal, so it has been specifcally
excluded.

Some may question the use of the static keyword in
multimethod implementation function parameters. These keywords aren't
strictly required - the compiler knows whether a function definition is a
multimethod implementation by seeing whether it matches a previously
occurring multimethod declaration.

However, it is important to distinguish multimethod implementation
functions from coventional functions, because they are not visible directly.
Furthemore, the use of a special syntax enables the compiler to give a
warning when the user mis-types the name of a multimethod implementation so
that it doesn't match a multimethod declaration.

While static is already used for many different concepts in
C++, it has a useful similarity with the phrase static type, and it
is not currently used inside function parameters declarations and so its
usage here doesn't cause confusion.

There are two problems with this. First, casting to the base-most
parameters will give a base-most implementation (if it exists), which has the
curious result that casting the multimethod to its own type can give a
different function - the implementation that takes all base parameters:

The second problem is that the specified types in the
static_cast must match an implementation exactly. This is
contrary to the way qualified virtual fucntions work - calling
base::some_fn() will look for some_fn in class
base or any of base's base classes.

For speed critical loops, it might be useful to provide a way for the user
to get a pointer to the best multimethod implementation function for a
particular set of dynamic types. This enables client code that calls a
multimethod on the same parameters many times in a loop, to cache the
function pointer and so avoid any dispatch overhead.

For example, Cmm implements this as an extra dispatch function with the
same name as the multimethod, but with a suffix cmm_getimpl.

If support for dynamic loading of code is added to the C++ standard in the
future, then multimethod implementations in shared libraries/DLLs should be
automatically registered/deregistered when shared libraries/DLLs are
loaded/unloaded. The Cmm implementation already does this for Unix and Cygwin
platforms.

If a multimethod implementation takes a virtual parameter that derives
multiply from the base class used in the multimethod's corresponding virtual
parmeter, then it will never be possible for that mutimethod to be called
because down-casting from the base class to the derived class is ambiguous.
Maybe this should thus cause a diagnostic at compile time?

If one restricts oneself to Standard C++, it is possible to approximate
multimethods at the expense of verbosity in source code. See Item 31 in Scott
Meyers' More Effective C++, and chapter 11 of Andrei
Alexandrescu's Modern C++ Design, where template techniques are
used extensively. Bill Weston has a slightly different template technique
that supports non-exact matches, see http://homepage.ntlworld.com/w.weston/.

Cmm (See http://www.op59.net/cmm/readme.html)
is a source-code processor which implements a multimethods language extension
for C++. The description of Cmm's implementation here corresponds to cmm-0.26
(8 September 2003).

Cmm takes individual compilation units containing multimethod code that
have already been run through the C++ preprocessor (e.g.
#include's have been expanded), and generates legal C++
compilation units, which can then be compiled and linked together
conventionally.

The generated C++ code calls some support functions that are provided as a
single source file called dispatch.cpp. This contains functions
that manage data structures that store all known multimethod functions and
their implementations, the actual runtime dispatch functions, functions to
support dispatch caching and also support for the exception types thrown for
ambiguous/unmatched dispatches.

Cmm has been designed to generate multimethod code that supports dynamic
loading and unloading of code, which means that all information about
multimethods and their implementations are stored in dynamic data strucures.
This is probably not directly relevent to this proposal, because the C++
standard doesn't concern itself with dynamic loading of code. However, it
gives an example of the flexibility that the multimethods model can give.

Such implementation issues are not directly part of this proposal, but Cmm
demonstrates that multimethods need not break the simple C compiler/linker
model.

In order to perform multimethod dispatch, one has to first decide which
multimethod implementations match the dynamic types, and then try to find one
of the multimethod implementations which can be considered a better match
then all of the others.

The first step is done by calling auxiliary functions that Cmm creates for
each multimethod implementation function, which take the base parameters and
return true only if each of the parameters can be
dynamic_cast-ed to the multimethod implementation's parameters.
Because these functions takes the virtual function's base parameters, we
cannot use conventional overloading to distinguish them, and so Cmm makes the
function names unique using a mangling scheme which, for simplicity, will be
denoted by _XYZ in the following:

This separate function is generated in the same compilation unit as the
multimethod implementation, which enables the dynamic_cast to
work with derived types defined in anonymous namespaces. [Actually, the
overlap_cmm_match_XYZ function takes an array of two
void*'s rather than a separate parameter for each virtual type,
each of which is static_cast-ed to shape* before
the dynamic_cast is attempted. This is to enable generic
dispatch code to be used for different virtual functions.]

The second step requires that the inheritance tree for each dynamic type
is known. The dispatch code can then compare the types taken by each matching
multimethod implementation, and select the multimethod implementation for
which each virtual parameter is no less derived than any other matching
multimethod implementation's virtual parameter. As discussed earlier, this
corresponds to the way conventional overloading works at compile time.

The information about the inheritance trees is encoded in C-style strings
using a mangling scheme similar to that used by C++ compilers when generating
symbol names. This allows static initialisation to be used to register
multimethod implementations at runtime.

[Cmm can also register multimethod implementations at build time by
requiring a separate link-style invocation, but this made builds very
complicated and slow, and precludes use with dynamic loading of code. The
only advantages of this scheme are that dispatch time may be slightly faster,
and all multimethod implementations are usable by static initialisation
code.]

Finally, the generic dispatch code calls the actual multimethod
implementation via a wrapper function that takes the base parameters, casts
them directly to the derived types, and calls the multimethod implementation.
Again, this function name is mangled:

This function's precondition is that the derived types are correct and so
the static_cast's are legal. Using this wrapper function enables
the dispatch code to work in terms of generic function pointers even if
multimethod implementations use derived classes in anonymous namespace.

[The function should use dynamic_cast rather than
static_cast when derived inherits virtually from
base, but this hasn't been implemented yet.]

cmm_implementation_holder is a class defined in
dispatch.h/dispatch.cpp, whose constructor
de-mangles the first two textual parameters to extract complete information
about the inheritance tree for each virtual parameter taken by the virtual
function and the implementation function. Together with the
overlap_cmm_match functions, this is sufficient to enable
multimethod dispatch to be performed.

In this example, the first mangled string means: "A function called
overlap with 2 virtual parameters, the first a class containing one
item in its inheritance tree, shape, and the second also containing
the same single class in its inheritance tree, shape". The second
mangled string means: "A function called overlap_ with 2 virtual
parameters, the first one being a class with 2 items in its inheritance tree,
shape followed by square, while the second parameter's type
also contains 2 items in its inheritance tree, the first one being
shape, and the second triangle".

This use of static initialisers to register implementations allows
dynamically loaded code to automatically register new implementations with
the dispatch functions. Furthermore, the destructor of the
cmm_implementation_holder class unregisters the implementations,
so one can load/unload code at will.

The handling of implementation functions in dynamically loaded code has
been succesfully tested on OpenBSD 3.2, OpenBSD 3.3, Slackware Linux 8.1 and
Cygwin/Windows, using the dlopen() and dlclose()
functions.

Figuring out which of a set of implementation functions to call for a
particular set of dynamic types is very slow, so some sort of caching scheme
is required. Caching is performed by the code in the
dispatch.cpp library. Currently this uses a
std::map to map from a std::vector of
std::type_info's to a function pointer. This gives a dispatch
speed of O(Log N), where N is the number of different combinations of dynamic
types that have been encountered (some of which may be mapped to the same
function pointer). On OpenBSD 3.2 with gcc 2.95, the dispatch time for two
virtual parameters is around 10-100 times slower than a conventional virtual
function call.

It would probably be better to have special cache support for multimethods
with one or two virtual parameters, using a std::map with key
types std::type_info[1] and std::type_info[2]. No
doubt templates could be used to do this with maximum obfuscation.

The actual virtual dispatch function is very simple, because it uses code
in dispatch.cpp to do all the real work. This means that it is
practical to generate a separate copy in all compilation units as an inline
function, looking like:

The cmm_lookup function uses types as an index
into the internal std::map dispatch cache. If this fails, the
actual parameters params are used in the slow lookup algorithm
described earlier. It returns a generic function pointer, which has to be
cast into the correct type using reinterpret_cast.

Cmm provides an extra dispatch function that doesn't actually call the
implementation. Instead, it returns a pointer to the best implementation
function. This enables client code that calls a multimethod on the same
parameters many times in a loop, to cache the function pointer and so avoid
any dispatch overhead.

This extra dispatch function has the same name as the virtual function,
with a suffix _cmm_getimpl. Using the overlap
example, if you have one collection of shapes that you know are all squares,
and you want to search for an overlap with a particular shape, you would
usually do:

As discussed earlier, it is possible to get constant-time dispatch speed
if all types are assigned a unique small integer, by looking in a
multi-dimensional array using the small integers as indices. Cmm has a
command-line switch that makes the generated code use this technique.

In pseudo code, the dispatch for a function with two virtual parameters
looks like:

To reduce memory usage, Cmm's dispatch.cpp contains an
implementation of this dispatch method that allocates the array lazily so
that memory is only allocated for those rows that are actually used.

Getting a unique small integer for each dynamic type is slightly tricky.
In an ideal world, the compiler and linker would conspire to make space
available in the vtable, which would enable very fast lookup. Cmm can't do
this though, so instead it inserts an inline virtual function body into all
polymorphic classes, containing a static int to enable fast access to the
unique integers:

The function cmm_get_small_integer() is in the Cmm library
dispatch.cpp along with all of the other support function. It
maintains an internal map of std::type_info's to
ints so that it returns the same integer if called more than
once for the same type. This is required to make things work when the C++
environment doesn't implement the static int id correctly; for
example, under OpenBSD 3.2 and 3.3, each compilation unit that contains the
inline function cmm_get_small_integer() will have its own
copy.

Cmm's constant time dispatch system is not robust. It adds a virtual
function to all polymorphic classes, but this only works if all code is
passed through Cmm. Other code, such as code in libraries that are linked
into the final executable, may break at runtime because of assuming a
different vtable layout. To avoid breaking code in system libraries, Cmm
doesn't insert the function into polymorphic classes defined in the
std namespace, but of course this means that you cannot do
constant-time multimethod dispatch on base classes that are defined in
std, such as std::exception.