Multiple Dispatch

A method can have more than one virtual parameter. This is often called "multi-methods" or "multiple dispatch". All the virtual parameters participate equally in overrider selection, following the same rules as those governing overload resolution - except that the selection happens at runtime, and takes into account the argument’s dynamic types.

Multiple dispatch is occasionally useful. When it is needed, it can be difficult to implement correctly and efficiently by hand. For example, given the following classes:

struct Role {
    virtual ~Role() = default;
};

struct Employee : Role {};
struct Salesman : Employee {};
struct Manager : Employee {};
struct Founder : Role {};

struct Expense {
    virtual ~Expense() {
    }
};

struct Public : Expense {};
struct Bus : Public {};
struct Metro : Public {};
struct Taxi : Expense {};
struct PrivateJet : Expense {};

We want to implement a function - approve - that determines who can make what kind of expenses. The rules are:

  • By default, an expense is rejected.

  • Employees can take any public transportation.

  • Managers can also take a taxi, for a ride cost up to $100.

  • Founders can take any transportation, including a private jet.

This is a case of multiple dispatch: the outcome depends on two parameters. It can be implemented as a method with two virtual arguments. The four rules can be expressed as four overriders:

using boost::openmethod::virtual_ptr;

BOOST_OPENMETHOD(
    approve, (virtual_ptr<const Role>, virtual_ptr<const Expense>, double),
    bool);

BOOST_OPENMETHOD_OVERRIDE(
    approve, (virtual_ptr<const Role>, virtual_ptr<const Expense>, double),
    bool) {
    return false;
}

BOOST_OPENMETHOD_OVERRIDE(
    approve, (virtual_ptr<const Employee>, virtual_ptr<const Public>, double),
    bool) {
    return true;
}

BOOST_OPENMETHOD_OVERRIDE(
    approve,
    (virtual_ptr<const Manager>, virtual_ptr<const Taxi>, double amount),
    bool) {
    return amount < 100.0;
}

BOOST_OPENMETHOD_OVERRIDE(
    approve, (virtual_ptr<const Founder>, virtual_ptr<const Expense>, double),
    bool) {
    return true;
}

int main() {
    boost::openmethod::initialize();

    Founder bill;
    Employee bob;
    Manager alice;

    Bus bus;
    Taxi taxi;
    PrivateJet jet;

    std::cout << std::boolalpha;
    std::cout << approve(bill, bus, 4.0) << "\n";        // true
    std::cout << approve(bob, bus, 4.0) << "\n";         // true
    std::cout << approve(bob, taxi, 36.0) << "\n";       // false
    std::cout << approve(alice, taxi, 36.0) << "\n";     // true
    std::cout << approve(alice, taxi, 2000.0) << "\n";   // false
    std::cout << approve(bill, jet, 120'000.0) << "\n";  // true
    std::cout << approve(bob, jet, 120'000.0) << "\n";   // false
    std::cout << approve(alice, jet, 120'000.0) << "\n"; // false
}

Because approve understands inheritance, we don’t have to specify an overrider for every combination (5 role classes x 6 expense classes = 30 combinations).

Ambiguities

Just like with overload resolution, ambiguities can arise. Let’s look at another example - matrix addition:

#include <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>
#include <iostream>

struct Matrix { virtual ~Matrix() = default; };
struct DenseMatrix : Matrix {};
struct SparseMatrix : Matrix {};

BOOST_OPENMETHOD_CLASSES(Matrix, DenseMatrix, SparseMatrix);

using boost::openmethod::virtual_ptr;

BOOST_OPENMETHOD(
    add, (virtual_ptr<const Matrix>, virtual_ptr<const Matrix>), void);

BOOST_OPENMETHOD_OVERRIDE(
    add, (virtual_ptr<const Matrix>, virtual_ptr<const SparseMatrix>), void) {
}

BOOST_OPENMETHOD_OVERRIDE(
    add, (virtual_ptr<const SparseMatrix>, virtual_ptr<const Matrix>), void) {
}

int main() {
    boost::openmethod::initialize();

    SparseMatrix a, b;
    add(a, b);
}

The programs terminates with the following error message:

ambiguous
Aborted (core dumped)

This is because the call to add(a, b) is ambiguous: both overriders are equally good matches. The solution is to add an overrider for the case where both arguments are SparseMatrix:

BOOST_OPENMETHOD_OVERRIDE(
    add, (virtual_ptr<const SparseMatrix>, virtual_ptr<const SparseMatrix>), void) {
}