Untitled :: Boost.OpenMethod

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 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_ptr`s.

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
    }

Edit this Page