Untitled :: Boost.OpenMethod

## Alternatives to virtual_ptr

Virtual arguments can also be passed as plain references, plain pointers, or smart pointers, i.e. without using `virtual_ptr`. In a method declaration, parameters with a type wrapped in class template `virtual_` are considered in overrider selection (along with `virtual_ptr` parameters).

For example, the `Node` example can be rewritten as follows:

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

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

    using boost::openmethod::virtual_;

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

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

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

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

    BOOST_OPENMETHOD_CLASSES(Node, Variable, Plus, Times);

    auto main() -> int {
        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
    }

Note that `virtual_` is not used in the overrider. It is just a decorator used in the method. It is also removed from the method’s signature. `BOOST_OPENMETHOD` generates:

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

### `boost_openmethod_vptr`

By itself, `virtual_` does not provide any benefits. Passing the virtual argument by reference almost compiles to the same code as creating a `virtual_ptr`, using it for one call, then throwing it away. The only difference is that the virtual argument is passed as one pointer instead of two.

However, we can now customize how the vptr is obtained. When the method sees a `virtual_` parameter, it looks for a `boost_openmethod_vptr` function that can be called with the virtual argument (passed by const reference) and a pointer to a registry, and returns a `vptr_type`. If one is found, it is called to acquire the vptr.

In the following example, we embed the vptr in the object, just like the vptr for native virtual functions. The v-table for a registered class can be found via a variable template `static_vptr`, nested in class `default_registry`. Classes used in `virtual_` parameters need not be polymorphic, in the C++ sense. This time we implement `value` as an open-method. Nodes don’t have any virtual functions anymore.

    struct Node {
        boost::openmethod::vptr_type vptr;
        using registry = boost::openmethod::default_registry; // short alias

        friend auto boost_openmethod_vptr(const Node& node, registry*) {
            return node.vptr;
        }

        Node() {
            vptr = registry::static_vptr<Node>;
        }
    };

    struct Variable : Node {
        Variable(int value) : v(value) {
            vptr = registry::static_vptr<Variable>;
        }

        int v;
    };

    struct Plus : Node {
        Plus(const Node& left, const Node& right) : left(left), right(right) {
            vptr = registry::static_vptr<Plus>;
        }

        const Node& left;
        const Node& right;
    };

    struct Times : Node {
        Times(const Node& left, const Node& right) : left(left), right(right) {
            vptr = registry::static_vptr<Times>;
        }

        const Node& left;
        const Node& right;
    };

    #include <boost/openmethod/initialize.hpp>

    using boost::openmethod::virtual_;

    BOOST_OPENMETHOD(value, (virtual_<const Node&> node), int);

    BOOST_OPENMETHOD_OVERRIDE(value, (const Variable& var), int) {
        return var.v;
    }

    BOOST_OPENMETHOD_OVERRIDE(value, (const Plus& plus), int) {
        return value(plus.left) + value(plus.right);
    }

    BOOST_OPENMETHOD_OVERRIDE(value, (const Times& times), int) {
        return value(times.left) * value(times.right);
    }

    BOOST_OPENMETHOD_CLASSES(Node, Variable, Plus, Times);

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

A call to `value`:

    int call_value(const Node& node) {
        return value(node);
    }

…​compiles to:

    mov	rax, qword ptr [rdi]
    mov	rcx, qword ptr [rip + value::fn+88]
    jmp	qword ptr [rax + 8*rcx]         # TAILCALL

### `inplace_vptr`

`inplace_vptr_base` and `inplace_vptr_derived` automate the creation and management of embedded vptrs. They are both CRTP mixin classes.

`inplace_vptr_base` is used at the root of a hierarchy. It defines a vptr member variable, and a `boost_openmethod_vptr` friend function that returns its value - just like in the previous example. `inplace_vptr_derived` is used in derived classes. Their constructors set the vptr member to point to v-table for the class.

WARNING

  `inplace_vptr_derived` must be used at each level of the hierarchy, and reflect the actual inheritance relationships. Otherwise, the vptrs will not be set correctly, and the wrong overriders may be called.

    struct Node : boost::openmethod::inplace_vptr_base<Node> {
    };

    struct Variable : Node, boost::openmethod::inplace_vptr_derived<Variable, Node> {
        Variable(int value) : v(value) {}

        int v;
    };

    struct Plus : Node, boost::openmethod::inplace_vptr_derived<Plus, Node> {
        Plus(const Node& left, const Node& right) : left(left), right(right) {}

        const Node& left;
        const Node& right;
    };

    struct Times : Node, boost::openmethod::inplace_vptr_derived<Times, Node> {
        Times(const Node& left, const Node& right) : left(left), right(right) {}

        const Node& left;
        const Node& right;
    };

The rest of the program is as before, except that `BOOST_OPENMETHOD_CLASSES` needs not be called: `inplace_vptr_base` and `inplace_vptr_derived` take care of registering the classes and their inheritance relationships.

`inplace_vptr_derived` supports multiple inheritance: more than one base class can be listed after the class being defined.

The destructor of `inplace_vptr_derived` set the bases' vptrs back to the v-table for the bases, just like what C++ does for its native vptrs.

`inplace_vptr_base` and `inplace_vptr_derived` are aliased in `namespace boost::openmethod::aliases`.

Edit this Page