Untitled :: Boost.OpenMethod

OpenMethod provides a macro-free interface: the core API. This is useful in certain situations, for example when combining open-methods and templates.

Let’s rewrite the `postfix` method using the core API. An open-method is implemented as an instance of the `method` class template. Its parameters are an identifier type and a function type:

    #include <boost/openmethod/core.hpp>

    struct postfix_method_id;

    using postfix = method<
        postfix_method_id, void(virtual_ptr<const Node> node, std::ostream& os)>;

The `postfix_method_id` class acts as the method’s identifier: it separates it from other methods with the same signature. For example, we could have another method, `prefix`, which writes expressions in prefix notation. It would have the same signature. Without the identifier argument, `prefix` and `postfix` would be the same method.

The exact name of the identifier class does not matter. The class needs not be defined, only declared.

Inventing identifier class names can get tedious, so OpenMethod provides a macro for that: BOOST_OPENMETHOD_ID. Let’s use it:

    #include <boost/openmethod/macros.hpp>

    struct BOOST_OPENMETHOD_ID(postfix);

    using postfix = method<
        BOOST_OPENMETHOD_ID(postfix),
        void(virtual_ptr<const Node> node, std::ostream& os)>;

We said macro-free interface, but here is a macro again! Well, we are not forced to use the macro. There is a benefit though: it is used in the implementation of high-level macros like BOOST_OPENMETHOD. This makes it possible to mix the two styles, for example to define a method using the macro, and add overriders using the core API.

We call the method via the nested function object `fn`:

    postfix::fn(node, std::cout);

Overriders are ordinary functions. We add them to the method via the class template `override`, nested in the method class. Its constructor adds the functions passed as template arguments to the method.

    auto postfix_variable(virtual_ptr<const Variable> node, std::ostream& os) {
        os << node->v;
    }

    static postfix::override<postfix_variable> override_postfix_variable;

Once again we find ourselves inventing a name for a single use. Maybe some day C++ will get a Python-like `_` special variable. In the meantime, we can use another convenience macro: BOOST_OPENMETHOD_REGISTER. It takes a class, and instantiates a static object with an obfuscated name:

    #include <boost/openmethod/macros.hpp>

    auto postfix_plus(virtual_ptr<const Plus> node, std::ostream& os) {
        postfix::fn(node->left, os);
        os << ' ';
        postfix::fn(node->right, os);
        os << " +";
    }

    auto postfix_times(virtual_ptr<const Times> node, std::ostream& os) {
        postfix::fn(node->left, os);
        os << ' ';
        postfix::fn(node->right, os);
        os << " *";
    }

    BOOST_OPENMETHOD_REGISTER(postfix::override<postfix_plus, postfix_times>);

Again, use of this macro is completely optional.

We register the classes with `use_classes`:

    BOOST_OPENMETHOD_REGISTER(use_classes<Node, Variable, Plus, Times>);

Finally, we call the method via the static member of the method class `fn`:

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

### Methods and Templates

The primary purpose of the core API is to make open-methods inter-operate with templates.

`Plus` and `Times` are obvious candidates for templatization. They only differ by the operation they perform, and we already have templates for that in the standard library:

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

    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;
    };

    template<typename Op>
    struct BinaryOp : Node {
        BinaryOp(const Node& left, const Node& right) : left(left), right(right) {}
        int value() const override { return Op()(left.value(), right.value()); }
        const Node& left;
        const Node& right;
    };

Let’s go back to defining postfix using `BOOST_OPENMETHOD`. It is more convenient, as it generates `postfix` as an ordinary inline function, which can be overloaded, passed around by address, etc. The overrider for `Variable` does not involve templates, so we can write it as before:

    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;
    }

We write a function template that renders binary operations in postfix notation:

    template<class BinaryOp, char Op>
    auto postfix_binary(virtual_ptr<const BinaryOp> node, std::ostream& os) {
        postfix(node->left, os);
        os << ' ';
        postfix(node->right, os);
        os << " " << Op;
    }

Macro BOOST_OPENMETHOD_TYPE takes the same parameters as `BOOST_OPENMETHOD`, and expands to the core `method` instance. That is how we access its nested `overrider` class template:

    BOOST_OPENMETHOD_TYPE(
        postfix, (virtual_ptr<const Node> node, std::ostream& os), void)::
        override<
            postfix_binary<BinaryOp<std::plus<int>>, '+'>,
            postfix_binary<
                BinaryOp<std::multiplies<int>>, '*'>> add_binary_overriders;

We can register the classes with `BOOST_OPENMETHOD_CLASSES`, and write `main` like so:

    BOOST_OPENMETHOD_CLASSES(
        Node, Variable, BinaryOp<std::plus<int>>, BinaryOp<std::multiplies<int>>);

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

This example only scratches the surface of what can be done by combining templates, the core API, and libraries such as Boost.Mp11.

Edit this Page