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