An open-method is a free-standing function that has one or more virtual parameters. When it is called, it forwards to an overrider selected from a set by examining the dynamic types of the virtual parameters.

If this sounds like a virtual function, that’s because because an open-method with one virtual parameter is equivalent to a virtual function - with one big difference: it exists outside of classes.

A virtual parameter is in the form virtual_ptr<Class>. It is a pointer-like class that points to an instance of Class. virtual_ptr is defined in the library’s main namespace, boost::openmethod.

To create an open-method that implements the postfix operation, we use the BOOST_OPENMETHOD macro:

BOOST_OPENMETHOD(
    postfix, (virtual_ptr<const Node> node, std::ostream& os), void);

postfix is the method’s name. It takes one virtual parameter, node, and one non-virtual parameter, os. It returns void. The macro generates the following function:

inline auto postfix(virtual_ptr<const Node> node, std::ostream& os) -> void {
    // examine the type of *node
    // select an overrider
    // call it
}

Before we can call the method, we need to define overriders. For that we use the BOOST_OPENMETHOD_OVERRIDE macro:

BOOST_OPENMETHOD_OVERRIDE(
    postfix, (virtual_ptr<const Variable> var, std::ostream& os), void) {
    os << var->v;
}

The overrider must have virtual parameters in the same positions as in the method. The classes in the method’s virtual parameters must be accessible, non-ambiguous bases of the classes in the overrider. Note that this rules out repeated inheritance. Apart from this restriction, multiple and virtual inheritance are supported.

The non-virtual parameters must have exactly the same types as in the method.

Let’s look at another overrider:

BOOST_OPENMETHOD_OVERRIDE(
    postfix, (virtual_ptr<const Plus> plus, std::ostream& os), void) {
    postfix(plus->left, os);
    os << ' ';
    postfix(plus->right, os);
    os << " +";
}

This one calls postfix recursively to print the left and right sub-expressions. Note that we call the method just like an ordinary function.

postfix expects a virtual_ptr<const Node>, and we are passing it a plain reference to a Node object. This works because virtual_ptr has conversion constructors from plain references or pointers to objects, or from other virtual_ptrs.

There are two more things we need to do.

OpenMethod is a library, not a compiler. It needs to be informed of all the classes that may be used as virtual parameters, and in method calls, and their inheritance relationships. We provide that information with the BOOST_OPENMETHOD_CLASSES macro:

BOOST_OPENMETHOD_CLASSES(Node, Variable, Plus, Times);

Classes can be registered multiple times, in any order, and incrementally. Every direct base of a class must appear together with it in at least one call to BOOST_OPENMETHOD_CLASSES. This enables the library to deduce the complete inheritance lattice.

The constructs used in this example require the classes to be polymorphic, in the standard C++ sense, i.e. they must have at least one virtual function. The library can also be used with non-polymorphic classes, with some restrictions.

Finally, we need to call boost::openmethod::initialize() before the first call to an open-method. This builds the dispatch tables used during method calls. It is typically done once, at the very beginning of main, and requires the inclusion of <boost/openmethod/initialize.hpp>.

#include <boost/openmethod/initialize.hpp>

int main() {
    boost::openmethod::initialize();
    // ...
}

Putting it all together:

struct Node {
    virtual ~Node() {}
    virtual int value() const = 0;
};

struct Variable : Node {
    Variable(int value) : v(value) {}
    int value() const override { return v; }
    int v;
};

struct Plus : Node {
    Plus(const Node& left, const Node& right) : left(left), right(right) {}
    int value() const override { return left.value() + right.value(); }
    const Node& left; const Node& right;
};

struct Times : Node {
    Times(const Node& left, const Node& right) : left(left), right(right) {}
    int value() const override { return left.value() * right.value(); }
    const Node& left; const Node& right;
};

// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// library code
// =============================================================================
// application code
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

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

using boost::openmethod::virtual_ptr;

BOOST_OPENMETHOD(postfix, (virtual_ptr<const Node> node, std::ostream& os), void);

BOOST_OPENMETHOD_OVERRIDE(
    postfix, (virtual_ptr<const Variable> var, std::ostream& os), void) {
    os << var->v;
}

BOOST_OPENMETHOD_OVERRIDE(
    postfix, (virtual_ptr<const Plus> plus, std::ostream& os), void) {
    postfix(plus->left, os);
    os << ' ';
    postfix(plus->right, os);
    os << " +";
}

BOOST_OPENMETHOD_OVERRIDE(
    postfix, (virtual_ptr<const Times> times, std::ostream& os), void) {
    postfix(times->left, os);
    os << ' ';
    postfix(times->right, os);
    os << " *";
}

BOOST_OPENMETHOD_CLASSES(Node, Variable, Plus, Times);

int main() {
    boost::openmethod::initialize();
    Variable a{2}, b{3}, c{4};
    Plus d{a, b}; Times e{d, c};
    postfix(e, std::cout);
    std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20
}