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 wrappted 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 way. 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_derivedmust 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.