Introduction
Open-methods are similar to virtual functions, but they are not required to be members of a class. By being both free and virtual, they provide a solution to the Expression Problem:
Given a set of types, and a set of operations on these types, is it possible to add new operations on the existing types, and new types to the existing operations, without modifying existing code?
As a bonus, open-methods can take more than one argument into account when selecting the appropriate function to call - aka multiple dispatch. For that reason, open-methods are often called multi-methods, but that term is misleading, as it suggests that the feature is useful only when multiple dispatch is needed. In reality, it has been observed that, in large systems written in languages that support multi-methods, most methods use single-dispatch. The real benefit is in the solution to the Expression Problem.
Open-methods were introduced by the Common Lisp Object System, and they are native to many languages: Clojure, Julia, Dylan, TADS, Cecil, Diesel, Nice, etc. Bjarne Stroustrup wanted open-methods in C++ almost from the beginning. In D&E he writes:
I repeatedly considered a mechanism for a virtual function call based on more than one object, often called multi-methods. I rejected multi-methods with regret because I liked the idea, but couldn’t find an acceptable form under which to accept it. […] Multi-methods is one of the interesting what-ifs of C++. Could I have designed and implemented them well enough at the time? Would their applications have been important enough to warrant the effort? What other work might have been left undone to provide the time to design and implement multi-methods? Since about 1985, I have always felt some twinge of regret for not providing multi-methods (Stroustrup, 1994, The Design and Evolution of C++, 13.8).
Circa 2007, he and his PhD students Peter Pirkelbauer and Yuriy Solodkyy wrote a series of papers and a prototype implementation based on the EDG compiler. Unfortunately, open-methods never made it into the standard. Stroustrup bemoans, in a more recent paper:
In retrospect, I don’t think that the object-oriented notation (e.g., x.f(y)) should ever have been introduced. The traditional mathematical notation f(x,y) is sufficient. As a side benefit, the mathematical notation would naturally have given us multi-methods, thereby saving us from the visitor pattern workaround (Stroustrup, 2020, Thriving in a Crowded and ChangingWorld: C++ 2006–2020).
This library implements the features described in the N2216 paper, with some extensions:
-
a mechanism for calling the next most specialized overrider
-
support for smart pointers
-
customization points for RTTI, error handling, tracing, smart pointers…
Multiple and virtual inheritance are supported, with the exception of repeated inheritance.
Tutorials
Hello World
Consider the following program, intended to demonstrate the basics of virtual functions:
#include <iostream>
#include <memory>
struct Animal {
Animal(std::string name) : name(name) {}
virtual ~Animal() = default;
virtual void poke(std::ostream&) = 0;
std::string name;
};
struct Cat : Animal {
using Animal::Animal;
void poke(std::ostream& os) override {
os << name << " hisses";
}
};
struct Dog : Animal {
using Animal::Animal;
void poke(std::ostream& os) override {
os << name << " barks";
}
};
struct Bulldog : Dog {
using Dog::Dog;
void poke(std::ostream& os) override {
Dog::poke(os);
os << " and bites back";
}
};
auto main() -> int {
std::unique_ptr<Animal> a(new Cat("Felix"));
std::unique_ptr<Animal> b(new Dog("Snoopy"));
std::unique_ptr<Animal> c(new Bulldog("Hector"));
a->poke(std::cout); // prints "Felix hisses"
std::cout << ".\n";
b->poke(std::cout); // prints "Snoopy barks"
std::cout << ".\n";
c->poke(std::cout); // prints "Hector barks and bites back"
std::cout << ".\n";
return 0;
}
We are going to rewrite this using open-methods.
First we remove the poke
virtual functions from the domain classes:
#include <string>
struct Animal {
Animal(std::string name) : name(name) {}
std::string name;
virtual ~Animal() = default;
};
struct Cat : Animal {
using Animal::Animal;
};
struct Dog : Animal {
using Animal::Animal;
};
struct Bulldog : Dog {
using Dog::Dog;
};
Note that the Animal classes do not depend on iostreams anymore. This is a major advantage of open-methods over virtual functions: they make it possible to better organize dependencies.
Let’s implement poke
. First we need to include the library’s main header. It
defines a few macros, and injects a name - virtual_ptr
- in the global
namespace.
#include <iostream>
#include <boost/openmethod.hpp>
BOOST_OPENMETHOD(
poke, // method name
(std::ostream&, virtual_ptr<Animal>), // method signature
void); // return type
This defines a free function called poke
, which takes two arguments. The first
is the ostream
. The second argument corresponds to the implicit this
pointer
in a virtual function. It is now an explicit argument. Just like with virtual
functions, the exact function to execute is selected on the basis of the
argument’s dynamic type.
Unlike virtual functions, there is no such thing as a pure open-method that would make a class abstract. It is not possible to determine if an overrider is available from looking at just the current translation unit.
Let’s add overriders for Cat
and Dog
:
BOOST_OPENMETHOD_OVERRIDE(
poke, // method name
(std::ostream & os, virtual_ptr<Cat> cat), // overrider signature
void) { // return type
os << cat->name << " hisses"; // overrider body
}
BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, virtual_ptr<Dog> dog), void) {
os << dog->name << " barks";
}
Bulldog::poke
calls the poke
it overrides in its Dog
base. The equivalent
for open-methods is next
, a function that is available only inside the body of
an overrider. It calls the next most specific overrider, i.e. what would have
been called if the overrider did not exist.
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Bulldog> dog), void) {
next(os, dog); // call base overrider
os << " and bites back";
}
All classes involved in open-method calls need to be registered using the
BOOST_OPENMETHOD_CLASSES
macro:
BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, Bulldog);
Classes can be registered incrementally, as long as all the direct bases of a
class are listed with it in some call(s) to BOOST_OPENMETHOD_CLASSES
. For
example, Bulldog
can be added in a second call, as long as Dog
is listed as
well:
// in animals.cpp
BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);
// in bulldog.cpp
BOOST_OPENMETHOD_CLASSES(Dog, Bulldog);
boost::openmethod::initialize();
must be called before any open-method call.
It builds the dispatch tables. Typically this is done in main
:
#include <boost/openmethod/compiler.hpp>
// only needed in the file that calls boost::openmethod::initialize()
auto main() -> int {
boost::openmethod::initialize();
// ...
}
We call poke
like any ordinary function. We can pass it the animals by
reference, because virtual_ptr
has a conversion constructor for that:
std::unique_ptr<Animal> felix(new Cat("Felix"));
std::unique_ptr<Animal> snoopy(new Dog("Snoopy"));
std::unique_ptr<Animal> hector(new Bulldog("Hector"));
poke(std::cout, *felix); // Felix hisses
std::cout << ".\n";
poke(std::cout, *snoopy); // Snoopy barks
std::cout << ".\n";
poke(std::cout, *hector); // Hector barks and bites
std::cout << ".\n";
Note
|
virtual_ptr is more like a reference than a pointer: it cannot be null,
and it cannot be re-assigned. The only reason why it is not called virtual_ref
is to save the name in case it becomes possible to overload the dot operator in
future versions of C++.
|
Multiple Dispatch
A method can have more than one virtual_ptr
parameter. For example:
BOOST_OPENMETHOD(
encounter,
(std::ostream&, virtual_ptr<Animal>, virtual_ptr<Animal>), void);
// 'encounter' catch-all implementation.
BOOST_OPENMETHOD_OVERRIDE(
encounter,
(std::ostream & os, virtual_ptr<Animal> a, virtual_ptr<Animal> b), void) {
os << a->name << " and " << b->name << " ignore each other";
}
// Add definitions for specific pairs of animals.
BOOST_OPENMETHOD_OVERRIDE(
encounter,
(std::ostream & os, virtual_ptr<Dog> dog1, virtual_ptr<Dog> dog2), void) {
os << "Both wag tails";
}
BOOST_OPENMETHOD_OVERRIDE(
encounter, (std::ostream & os, virtual_ptr<Dog> dog, virtual_ptr<Cat> cat),
void) {
os << dog->name << " chases " << cat->name;
}
BOOST_OPENMETHOD_OVERRIDE(
encounter, (std::ostream & os, virtual_ptr<Cat> cat, virtual_ptr<Dog> dog),
void) {
os << cat->name << " runs away from " << dog->name;
}
// cat and dog
encounter(std::cout, *felix, *snoopy); // Felix runs away from Snoopy
std::cout << ".\n";
// dog and cat
encounter(std::cout, *snoopy, *felix); // Snoopy chases Felix
std::cout << ".\n";
// dog and dog
encounter(std::cout, *snoopy, *hector); // Both wag tails
std::cout << ".\n";
// cat and cat
std::unique_ptr<Animal> tom(new Cat("Tom"));
encounter(std::cout, *felix, *tom); // Felix and Tom ignore each other
std::cout << ".\n";
The appropriate overrider is selected using a process similar to overload resolution, with fallback options. If one overrider is more specialized than all the others, call it. Otherwise, the return type is used as a tie-breaker, if it is covariant with the return type of the base method. If there is still no unique best overrider, one of the best overriders is chosen arbitrarily.
Friendship
Overriders are implemented as static functions located in specializations of a
template named after the method, declared in the same scope. Macro
BOOST_OPENMETHOD_OVERRIDERS
returns that name. The template argument for a
specialization is the signature of the overrider. For example, the overrider of
poke
for Cat
is:
BOOST_OPENMETHOD_OVERRIDERS(poke)<
void(std::ostream& os, virtual_ptr<Cat> cat)>::fn;
We can thus grant friendship to all the overriders of poke
:
class Cat;
class Dog;
class Animal {
// ...
private:
std::string name;
template<typename> friend struct BOOST_OPENMETHOD_OVERRIDERS(poke);
};
Be aware, though, that the overriders of any method called poke
- even with
a different signature - are granted friendship.
We can also grant friendship to individual overriders:
class Cat;
class Dog;
template<typename> struct BOOST_OPENMETHOD_OVERRIDERS(poke);
class Animal {
// ...
private:
std::string name;
friend struct BOOST_OPENMETHOD_OVERRIDERS(poke)<void(std::ostream&, virtual_ptr<Cat>)>;
friend struct BOOST_OPENMETHOD_OVERRIDERS(poke)<void(std::ostream&, virtual_ptr<Dog>)>;
};
Performance
Open-methods are almost as fast as ordinary virtual member functions when compiled with optimization.
clang compiles the following code:
void call_poke_via_ref(std::ostream& os, Animal& a) {
poke(os, a);
}
…to this on the x64 architecture (variable names have been shortened for readability):
mov rax, qword ptr [rsi]
mov rdx, qword ptr [rip + hash_mult]
imul rdx, qword ptr [rax - 8]
movzx ecx, byte ptr [rip + hash_shift]
shr rdx, cl
mov rax, qword ptr [rip + vptrs]
mov rax, qword ptr [rax + 8*rdx]
mov rcx, qword ptr [rip + poke::slots_strides]
mov rax, qword ptr [rax + 8*rcx]
jmp rax
llvm-mca estimates a throughput of 4 cycles per dispatch. Comparatively, calling a native virtual functions takes one cycle. However, the difference is amortized by the time spent passing the arguments and returning from the function; plus, of course, executing the body of the function.
Micro benchmarks suggest that dispatching an open-methods with a single virtual argument is between 30% and 50% slower than calling the equivalent virtual function, with an empty body and no other arguments.
However, call_poke
does two things: it constructs a virtual_ptr<Animal>
from
an Animal&
; and then it calls the method. The construction of the
virtual_ptr
is the costly part, as it involves a hash table lookup. Once that
price has been paid, the virtual_ptr
can be used multiple times. It is passed
to the overrider, which can make further method calls through it. It can be
stored in variables in place of plain pointers.
Let’s look at another example: an AST for an arithmetic calculator:
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
struct Node {
virtual ~Node() {}
};
struct Literal : Node {
explicit Literal(int value) : value(value) {}
int value;
};
struct Plus : Node {
Plus(virtual_ptr<Node> left, virtual_ptr<Node> right)
: left(left), right(right) {}
virtual_ptr<Node> left, right;
};
struct Negate : Node {
explicit Negate(virtual_ptr<Node> node) : child(node) {}
virtual_ptr<Node> child;
};
BOOST_OPENMETHOD(value, (virtual_ptr<Node>), int);
BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Literal> node), int) {
return node->value;
}
BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Plus> node), int) {
return value(node->left) + value(node->right);
}
BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Negate> node), int) {
return -value(node->child);
}
BOOST_OPENMETHOD_CLASSES(Node, Literal, Plus, Negate);
auto main() -> int {
boost::openmethod::initialize();
Literal one(1), two(2);
Plus sum(one, two);
Negate neg(sum);
std::cout << value(neg) << "\n"; // -3
return 0;
}
The Negate
overrider compiles to:
mov rdi, qword ptr [rsi + 8]
mov rsi, qword ptr [rsi + 16]
mov rax, qword ptr [rip + value::slots_strides]
call qword ptr [rdi + 8*rax]
neg eax
pop rcx
The first two instructions read the virtual_ptr
from this
- placing its
content in registers rdi
and rsi
.
The next two instructions are the method call proper. According to llvm-mca, they take one cycle - the same as a native virtual function call.
When we create the Plus
and Negate
nodes, we call the conversion
constructors of virtual_ptr<Node>
, which occur the cost of hash table lookups.
However, in this example, we know the exact types of the objects. In that case,
we can use final_virtual_ptr
to construct the virtual_ptr
using a single
instruction. For example:
Literal one(1);
Negate neg(boost::openmethod::final_virtual_ptr(one));
…compiles to:
;; construct Literal
lea rax, [rip + vtable for Literal+16]
mov qword ptr [rsp], rax
mov dword ptr [rsp+8], 1
;; construct Negate
mov rax, qword ptr [rip+static_vptr<Literal>] ; address of openmethod v-table
lea rcx, [rip+vtable for Negate+16] ; address of native v-table
mov qword ptr [rsp+16], rcx ; set native v-table
mov qword ptr [rsp+24], rax ; set openmethod v-table
mov rax, rsp ; address of 'one'
mov qword ptr [rsp+32], rax ; set vptr object pointer to 'one'
final_virtual_ptr
does not require its argument to have a polymorphic type.
Smart Pointers
virtual_ptr
can also be used in combination with smart pointers.
virtual_ptr<std::shared_ptr<Class>>
(aliased to shared_virtual_ptr<Class>
)
and virtual_ptr<std::unique_ptr<Class>>
(aliased to
unique_virtual_ptr<Class>
) deliver the convenience of automatic memory
management with the speed of virtual_ptr
. Convenience functions
make_shared_virtual
and make_unique_virtual
create an object and return a
smart virtual_ptr to it. Since the exact type of the object is known, the vptr
is read from a static variable, without incuring the cost of a hash table
lookup.
Here is a variaton of the AST example that uses dynamic allocation and unique pointers:
#include <iostream>
#include <memory>
#include <boost/openmethod.hpp>
#include <boost/openmethod/unique_ptr.hpp>
#include <boost/openmethod/compiler.hpp>
using boost::openmethod::unique_virtual_ptr;
using boost::openmethod::make_unique_virtual;
struct Node {
virtual ~Node() {}
};
struct Literal : Node {
Literal(int value) : value(value) {}
int value;
};
struct Plus : Node {
Plus(unique_virtual_ptr<Node> left, unique_virtual_ptr<Node> right)
: left(std::move(left)), right(std::move(right)) {}
unique_virtual_ptr<Node> left, right;
};
struct Negate : Node {
Negate(unique_virtual_ptr<Node> node) : child(std::move(node)) {}
unique_virtual_ptr<Node> child;
};
BOOST_OPENMETHOD(value, (virtual_ptr<Node>), int);
BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Literal> node), int) {
return node->value;
}
BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Plus> node), int) {
return value(node->left) + value(node->right);
}
BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Negate> node), int) {
return -value(node->child);
}
BOOST_OPENMETHOD_CLASSES(Node, Literal, Plus, Negate);
auto main() -> int {
boost::openmethod::initialize();
auto expr = make_unique_virtual<Negate>(
make_unique_virtual<Plus>(
make_unique_virtual<Literal>(1),
make_unique_virtual<Literal>(2)));
std::cout << value(expr) << "\n"; // -3
return 0;
}
Alternatives to virtual_ptr
Virtual arguments can be passed as plain references. In a method declaration,
parameters with a type decorated with virtual_
are considered in overrider
selection (along with virtual_ptr
parameters).
For example, the poke
open-method in the Animals example can be rewritten as:
struct Animal {
virtual ~Animal() = default;
};
struct Cat : Animal {};
using boost::openmethod::virtual_;
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) {
os << "hiss";
}
BOOST_OPENMETHOD_CLASSES(Animal, Cat);
int main() {
boost::openmethod::initialize();
Cat cat;
poke(std::cout, cat); // hiss
}
Note that virtual_
is not used in the overrider. It is also removed from the
method’s signature.
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 takes
the parameter (by const reference), and returns a vptr_type
. If one is found,
it is called to obtain the vptr. The vptr for a specific registered class can be
obtained via a variable template static_vptr
, nested in class default_policy
(more on policies below).
In the following example, we embed a vptr in the object, just like the vptr for native virtual functions:
class Animal {
protected:
boost::openmethod::vptr_type vptr;
friend auto boost_openmethod_vptr(const Animal& a) {
return a.vptr;
}
public:
Animal() {
vptr = boost::openmethod::default_policy::static_vptr<Animal>;
}
};
class Cat : public Animal {
public:
Cat() {
vptr = boost::openmethod::default_policy::static_vptr<Cat>;
}
};
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) {
os << "hiss\n";
}
BOOST_OPENMETHOD_CLASSES(Animal, Cat);
int main() {
boost::openmethod::initialize();
Cat cat;
poke(std::cout, cat); // hiss
}
Note
|
With this approach, classes need not be polymorphic. A virtual destructor might be needed for correct destruction of objects, but it is not required by the library. |
The with_vptr
CRTP class automates the creation and management of embedded
vptrs.
#include <boost/openmethod/with_vptr.hpp>
class Animal : public boost::openmethod::with_vptr<Animal> {
};
class Cat : public Animal, public boost::openmethod::with_vptr<Cat, Animal> {
};
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) {
os << "hiss\n";
}
int main() {
boost::openmethod::initialize();
Cat cat;
poke(std::cout, cat); // hiss
}
If with_vptr
is passed only the class being defined, it adds a vptr to it, and
defines a boost_openmethod_vptr
friend function. If more classes are passed,
they must be the direct bases of the class potentially involved in open-method
calls. Its constructor and destructor set the vptr to point to the v-table for
the class. with_vptr
also takes care of registering the classes, so this time
the call to BOOST_OPENMETHOD_CLASSES
is not needed.
Core API
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 Animals example using the core API. An open-method is
implemented as an instance of the method
template. Its parameters are a
function signature and a return type:
#include <boost/openmethod/core.hpp>
using namespace boost::openmethod;
class poke_openmethod;
using poke = method<
poke_openmethod(std::ostream&, virtual_<Animal&>), void>;
The poke_openmethod
class acts as the method’s identifier: it separates it
from other methods with the same signature. The exact name does not really
matter, and the class needs not be defined, only declared. Inventing a class
name can get tedious, so OpenMethod provides a macro for that:
#include <boost/openmethod/macros/name.hpp>
class BOOST_OPENMETHOD_NAME(poke);
using poke = method<
BOOST_OPENMETHOD_NAME(poke)(std::ostream&, virtual_ptr<Animal>), void>;
Note
|
BOOST_OPENMETHOD and associated macros use BOOST_OPENMETHOD_NAME in
their implementation. This makes it possible to mix the "macro" and "core"
styles.
|
We call the method via the nested function object fn
:
poke::fn(std::cout, animal);
Overriders are ordinary functions, added to a method using the nested template
override
:
auto poke_cat(std::ostream& os, virtual_ptr<Cat> cat) {
os << "hiss";
}
static poke::override<poke_cat> override_poke_cat;
Note
|
override can register multiple overriders.
|
In C++26, we will be able to use _
instead of inventing a one-time-use
identifier. In the meantime, OpenMethod provides a small convenience macro:
#include <boost/openmethod/macros/register.hpp>
auto poke_dog(std::ostream& os, virtual_ptr<Dog> dog) {
os << "bark";
}
BOOST_OPENMETHOD_REGISTER(poke::override<poke_dog>);
next
is available from the method’s nested next
template:
auto poke_bulldog(std::ostream& os, virtual_ptr<Bulldog> dog) -> void {
poke::next<poke_bulldog>(os, dog);
os << " and bite";
}
BOOST_OPENMETHOD_REGISTER(poke::override<poke_bulldog>);
Note
|
Since the function uses itself as a template argument in its body, its return type cannot be deduced. It must be specified explicitly, either by using the old function declaration style or a trailing return type. |
Why not call poke_dog
directly? We could; however, keep in mind that, in a
real program, a translation unit is not necessarily aware of the overriders
added elsewhere - especially in presence of dynamic loading.
We register the classes with use_classes
:
BOOST_OPENMETHOD_REGISTER(use_classes<Animal, Cat, Dog, Bulldog>);
Finally, we call the method via the static member of the method class fn
:
auto main() -> int {
boost::openmethod::initialize();
std::unique_ptr<Animal> a(new Cat);
std::unique_ptr<Animal> b(new Dog);
std::unique_ptr<Animal> c(new Bulldog);
poke::fn(std::cout, *a); // prints "hiss"
std::cout << "\n";
poke::fn(std::cout, *b); // prints "bark"
std::cout << "\n";
poke::fn(std::cout, *c); // prints "bark and bite"
std::cout << "\n";
return 0;
Policies and Facets
Methods and classes are scoped in a policy. A method can only reference classes registered in the same policy. If a class is used as a virtual parameter in methods using different policies, it must be registered with each of them.
Class templates use_classes
, method
, virtual_ptr
, and macros
BOOST_OPENMETHOD
and BOOST_OPENMETHOD_CLASSES
, accept an additional
argument, a policy class, which defaults to policies::debug
in debug builds,
and policies::release
in release builds.
A policy has a collection of facets. Facets control how type information is
obtained, how vptrs are fetched, how errors are handled and printed, etc. Some
are used in initialize
and method dispatch; some are used by other facets in
the same policy as part of their implementation. See the reference for a list of
facets and stock implementations. Policies and facets are placed in the
boost::openmethod::policies
namespace. Two policies are provided by the
library: release
and debug
.
release
contains the following facets:
facet | implementations | role |
---|---|---|
rtti |
std_rtti, minimal_rtti |
provides type information for classes and objects |
extern_vptr |
vptr_vector, vptr_map |
stores vptrs in an indexed collection |
type_hash |
fast_perfect_hash |
hash type id to an index in a vector |
error_handler |
vectored_error_handler, throw_error_handler |
handles errors |
policies::debug
contains the same facets as release
, plus a few more:
facet | implementation | role |
---|---|---|
runtime_checks |
(itself) |
enables runtime checks |
error_output |
basic_error_output |
prints error descriptions to |
trace_output |
basic_trace_output |
enables |
Policies, and some facets, have static variables. When it is the case, they are implemented as CRTP classes.
Policies can be created from scratch, using the basic_policy
template, or by
adding or removing facets from existing policies. For example, policies::debug
is a tweak of policies::release
:
namespace boost::openmethod::policies {
struct debug : release::add<
runtime_checks, basic_error_output<debug>, basic_trace_output<debug>> {};
}
boost::openmethod::default_policy
is an alias to release
or debug
,
depending on the value of preprocessor symbols NDEBUG
. The default policy can
be overriden by defining the macroprocessor symbol
BOOST_OPENMETHOD_DEFAULT_POLICY
before including
<boost/openmethod/core.hpp>
. The value of the symbol is used as a default
template parameter for use_classes
, method
, virtual_ptr
, and others. Once
the core
header has been included, changing BOOST_OPENMETHOD_DEFAULT_POLICY
has no effect. See below for examples.
Error Handling
When an error is encountered, the program is terminated by a call to abort
. If
the policy contains an error_handler
facet, it provides an error
member
function (or overloaded functions) to be called with an object identifying the
error. The release
and debug
policies implement the error facet with
vectored_error_handler
, which wraps the error object in a variant, and calls a
handler via a std::function
. By default, it prints a description of the error
to stderr
in the debug
policy, and does nothing in the release
policy. The
handler can be set with set_error_handler
:
#include <iostream>
#include <variant>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
struct Animal {
virtual ~Animal() = default;
};
struct Cat : Animal {};
struct Dog : Animal {};
BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);
BOOST_OPENMETHOD(trick, (std::ostream&, virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(
trick, (std::ostream & os, virtual_ptr<Dog> dog), void) {
os << "spin\n";
}
auto main() -> int {
namespace bom = boost::openmethod;
bom::initialize();
bom::default_policy::set_error_handler(
[](const bom::default_policy::error_variant& error) {
if (std::holds_alternative<bom::not_implemented_error>(error)) {
throw std::runtime_error("not implemented");
}
});
Cat felix;
Dog hector, snoopy;
std::vector<Animal*> animals = {&hector, &felix, &snoopy};
for (auto animal : animals) {
try {
trick(std::cout, *animal);
} catch (std::runtime_error& error) {
std::cerr << error.what() << "\n";
}
}
return 0;
}
Output:
spin
not implemented
spin
We can also replace the error_handler
facet with our own. For example:
#include <iostream>
#include <boost/openmethod/policies.hpp>
struct Animal {
virtual ~Animal() = default;
};
struct Cat : Animal {};
struct Dog : Animal {};
namespace bom = boost::openmethod;
struct throw_if_not_implemented : bom::policies::error_handler {
static auto error(const bom::openmethod_error&) -> void {
}
static auto error(const bom::not_implemented_error& err) -> void {
throw err;
}
};
struct throwing_policy
: bom::default_policy::fork<throwing_policy>::replace<
bom::policies::error_handler, throw_if_not_implemented> {};
#define BOOST_OPENMETHOD_DEFAULT_POLICY throwing_policy
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);
BOOST_OPENMETHOD(trick, (std::ostream&, virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(
trick, (std::ostream & os, virtual_ptr<Dog> dog), void) {
os << "spin\n";
}
auto main() -> int {
bom::initialize();
Cat felix;
Dog hector, snoopy;
std::vector<Animal*> animals = {&hector, &felix, &snoopy};
for (auto animal : animals) {
try {
trick(std::cout, *animal);
} catch (bom::not_implemented_error) {
std::cout << "not implemented\n";
}
}
return 0;
}
spin
not implemented
spin
Stock facet throw_error_handler
does this for all the exception types:
namespace boost::openmethod::policies {
struct throw_error_handler : error_handler {
template<class Error>
[[noreturn]] static auto error(const Error& error) -> void {
throw error;
}
};
} // namespace boost::openmethod::policies
Custom RTTI
Stock policies use the std_rtti
implementation of rtti
. Here is its full
source:
struct std_rtti : rtti {
template<typename T>
static type_id static_type() {
return reinterpret_cast<type_id>(&typeid(T));
}
template<typename T>
static type_id dynamic_type(const T& obj) {
return reinterpret_cast<type_id>(&typeid(obj));
}
template<class Stream>
static void type_name(type_id type, Stream& stream) {
stream << reinterpret_cast<const std::type_info*>(type)->name();
}
static std::type_index type_index(type_id type) {
return std::type_index(*reinterpret_cast<const std::type_info*>(type));
}
template<typename D, typename B>
static D dynamic_cast_ref(B&& obj) {
return dynamic_cast<D>(obj);
}
};
-
static_type
is used by class registration, byvirtual_ptr
's "final" constructs, and to format error and trace messages.T
is not restricted to the classes that appear as virtual parameters. This function is required. -
dynamic_type
is used to locate the v-table for an object. This function is usually required. If only thevirtual_ptr
"final" constructs are used, or ifboost_openmethod_vptr
is provided for all the classes in the policy, it can be omitted. -
type_name
writes a representation oftype
tostream
. It is used to format error and trace messages.Stream
is a lighweight version ofstd::ostream
with reduced functionality. It only supports insertion ofconst char*
,std::string_view
, pointers andstd::size_t
. This function is optional; if it is not provided, "type_id(type)" is used. -
type_index
returns an object that uniquely identifies a class. Some forms of RTTI (most notably, C++'stypeid
operator) do not guarantee that the type information object for a class is unique within the same program. This function is optional; if not provided,type
is assumed to be unique, and used as is. -
dynamic_cast_ref
castsobj
to classD
.B&&
is either a lvalue reference (possibly cv-qualified) or a rvalue reference.D
has the same reference category (and cv-qualifier if applicable) asB
. This function is required only in presence of virtual inheritance.
Consider a custom RTTI implementation:
struct Animal {
Animal(unsigned type) : type(type) {
}
virtual ~Animal() = default;
unsigned type;
static constexpr unsigned static_type = 1;
};
struct Cat : Animal {
Cat() : Animal(static_type) {
}
static constexpr unsigned static_type = 2;
};
// ditto for Dog
This scheme has an interesting property: its type ids are monotonically allocated in a small, dense range. Thus, we don’t need to hash them. We can use them as indexes in the table of vptrs.
This time we are going to replace the default policy globally. First we need to
define the custom RTTI facet. We must not include
<boost/openmethod/core.hpp>
or any header that includes it yet.
Here is the facet implementation:
namespace bom = boost::openmethod;
struct custom_rtti : bom::policies::rtti {
template<typename T>
static auto static_type() -> bom::type_id {
if constexpr (std::is_base_of_v<Animal, T>) {
return T::static_type;
} else {
return 0;
}
}
template<typename T>
static auto dynamic_type(const T& obj) -> bom::type_id {
if constexpr (std::is_base_of_v<Animal, T>) {
return obj.type;
} else {
return 0;
}
}
};
This facet is quite minimal. It does not support virtual inheritance. It would not produce good error or trace messages, because types would be represented by their integer ids.
This time we create a policy from scratch. For that we use the basic_policy
CRTP template:
struct custom_policy : bom::policies::basic_policy<
custom_policy, custom_rtti,
bom::policies::vptr_vector<custom_policy>> {};
#define BOOST_OPENMETHOD_DEFAULT_POLICY custom_policy
Now we can include the "core" header and write the example:
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Cat> cat), void) {
os << "hiss";
}
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Dog> dog), void) {
os << "bark";
}
BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);
auto main() -> int {
boost::openmethod::initialize();
std::unique_ptr<Animal> a(new Cat);
std::unique_ptr<Animal> b(new Dog);
poke(std::cout, *a); // prints "hiss"
std::cout << "\n";
poke(std::cout, *b); // prints "bark"
std::cout << "\n";
return 0;
}
This programs compiles even if standard RTTI is disabled.
Deferred RTTI
In the previous example, the RTTI system assigns types id statically. It is more
common to allocate them using a global counter, manipulated by static
constructors. This is a problem, because static_type
is used by class
registration. It may read the custom type ids before they are have been
initialized.
The solution is to add the deferred_static_rtti
facet to the policy; it defers
reading the type information until initialize
is called.
This time let’s support virtual inheritance as well. First the domain classes:
struct custom_type_info {
static unsigned last;
unsigned id = ++last;
};
unsigned custom_type_info::last;
struct Animal {
Animal() {
type = type_info.id;
}
virtual ~Animal() = default;
virtual auto cast_impl(unsigned target) -> void* {
if (type_info.id == target) {
return this;
} else {
return nullptr;
}
}
template<class Class>
auto cast() -> Class* {
return reinterpret_cast<Class*>(cast_impl(Class::type_info.id));
}
static custom_type_info type_info;
unsigned type;
};
custom_type_info Animal::type_info;
struct Cat : virtual Animal {
Cat() {
type = type_info.id;
}
virtual auto cast_impl(unsigned target) -> void* {
if (type_info.id == target) {
return this;
} else {
return Animal::cast_impl(target);
}
}
static custom_type_info type_info;
};
custom_type_info Cat::type_info;
// ditto for Dog
The rtti facet is the same, with one more function:
struct custom_rtti : bom::policies::rtti {
// as before
// to support virtual inheritance:
template<typename Derived, typename Base>
static auto dynamic_cast_ref(Base&& obj) -> Derived {
using base_type = std::remove_reference_t<Base>;
if constexpr (std::is_base_of_v<Animal, base_type>) {
return *obj.template cast<std::remove_reference_t<Derived>>();
} else {
abort(); // not supported
}
}
};
Finally, the policy contains an additional facet - deferred_static_rtti
:
struct custom_policy
: bom::policies::basic_policy<
custom_policy, custom_rtti,
bom::policies::deferred_static_rtti, // <-- additional facet
bom::policies::vptr_vector<custom_policy>> {};
The example is the same as in the previous section.
Dynamic Loading
OpenMethod supports dynamic loading on operating systems that are capable of
handling C++ templates correctly during dynamic link. A dynamic library can add
classes, methods and overriders to an existing policy. initialize
must then be
called to rebuild the dispatch tables.
This leads to a problem: any virtual_ptr
in existence before initialize
is
called again becomes invalid. This also applies to vptrs that are stored inside
objects by with_vptr
.
Note
|
This applies only to cases where a dynamic library adds to an existing policy. Even if the dynamic library itself uses open-methods, for example as an implementation detail, but it uses its own policy, there is no issue. |
The solution is to use a policy that contains the indirect_vptr
facet. Instead
of storing the vptr directly, it stores a reference to the vptr.
Here is an example:
// dl.hpp
#include <string>
#include <boost/openmethod.hpp>
struct Animal {
virtual ~Animal() {
}
};
struct Herbivore : Animal {};
struct Carnivore : Animal {};
struct Cow : Herbivore {};
struct Wolf : Carnivore {};
struct dynamic_policy
: boost::openmethod::default_policy::fork<dynamic_policy>::replace<
boost::openmethod::policies::extern_vptr,
boost::openmethod::policies::vptr_vector<
dynamic_policy, boost::openmethod::policies::indirect_vptr>> {};
template<class Class>
using dyn_vptr = boost::openmethod::virtual_ptr<Class, dynamic_policy>;
BOOST_OPENMETHOD(
encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string,
dynamic_policy);
Note
|
The policy must be passed to the method as well as the
virtual_ptr s.
|
The indirect_vptr
facet tells virtual_ptr
to use a reference to the vptr,
instead of its value. Even tough the value of the vptr changes when initialize
is called, the vptrs are stored in the same place (the policy’s
static_vptr<Class>
variables).
We can now register the classes and and provide an overrider:
// dl_main.cpp
#include <cstring>
#include <iostream>
#include <dlfcn.h>
#include <unistd.h>
#include <boost/openmethod.hpp>
#include <boost/openmethod/unique_ptr.hpp>
#include <boost/openmethod/compiler.hpp>
#include "dl.hpp"
BOOST_OPENMETHOD_CLASSES(
Animal, Herbivore, Cow, Wolf, Carnivore, dynamic_policy);
BOOST_OPENMETHOD_OVERRIDE(
encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string) {
return "ignore\n";
}
It is not necessary to pass the policy to BOOST_OPENMETHOD_CLASSES
, because
indirect_vptr
does not have any state. As for BOOST_OPENMETHOD_OVERRIDE
, it
deduces the policy from the method.
At this point we only have one overrider. Animals of all species ignore one another:
auto main() -> int {
using namespace boost::openmethod;
initialize<dynamic_policy>();
std::cout << "Before loading library\n";
auto gracie = make_unique_virtual<Cow, dynamic_policy>();
// Wolf _willy;
// auto willy = virtual_ptr<Wolf, dynamic_policy>(_willy);
auto willy = make_unique_virtual<Wolf, dynamic_policy>();
std::cout << "Gracie encounters Willy -> "
<< encounter(gracie, willy); // ignore
std::cout << "Willy encounters Gracie -> "
<< encounter(willy, gracie); // ignore
Let’s load a dynamic library containing this code:
// dl_shared.cpp
#include <string>
#include <boost/openmethod.hpp>
#include "dl.hpp"
BOOST_OPENMETHOD_OVERRIDE(
encounter, (dyn_vptr<Herbivore>, dyn_vptr<Carnivore>), std::string) {
return "run\n";
}
struct Tiger : Carnivore {};
BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore, dynamic_policy);
extern "C" auto make_tiger() -> Tiger* {
return new Tiger;
}
BOOST_OPENMETHOD_OVERRIDE(
encounter, (dyn_vptr<Carnivore>, dyn_vptr<Herbivore>), std::string) {
return "hunt\n";
}
Now back to main
:
char dl_path[4096];
dl_path[readlink("/proc/self/exe", dl_path, sizeof(dl_path))] = 0;
*strrchr(dl_path, '/') = 0;
strcat(dl_path, "/libdl_shared.so");
void* handle = dlopen(dl_path, RTLD_NOW);
if (!handle) {
std::cerr << "dlopen() failed: " << dlerror() << "\n";
exit(1);
}
std::cout << "\nAfter loading library\n";
boost::openmethod::initialize<dynamic_policy>();
auto make_tiger =
reinterpret_cast<Animal* (*)()>(dlsym(handle, "make_tiger"));
if (!make_tiger) {
std::cerr << "dlsym() failed: " << dlerror() << "\n";
exit(1);
}
std::cout << "Willy encounters Gracie -> "
<< encounter(willy, gracie); // hunt
{
auto hobbes = std::unique_ptr<Animal>(make_tiger());
std::cout << "Gracie encounters Hobbes -> "
<< encounter(gracie, *hobbes); // run
}
After unloading the library, we must call initialize
again:
dlclose(handle);
std::cout << "\nAfter unloading library\n";
boost::openmethod::initialize<dynamic_policy>();
std::cout << "Gracie encounters Willy -> "
<< encounter(gracie, willy); // ignore
std::cout << "Willy encounters Gracie -> "
<< encounter(willy, gracie); // ignore
Reference
Overview
Requirements
OpenMethod requires C++17 or above. It depends on the following Boost libraries:
-
Assert
-
Config
-
Core
-
DynamicBitset
-
Mp11
-
Preprocessor
Boost.Test is also required to build and run the unit tests.
Installation
The library is headers-only. You can install it system-wide, or add the path to
the include
directory to your project’s include path.
Namespaces
boost::openmethod
The library’s main namespace. Contains method
, virtual_ptr
and
virtual_ptr_traits
, use_classes
, the debug
and release
policies, etc.
boost::openmethod::policies
Contains the policy classes and their facets.
Headers
<boost/openmethod/core.hpp>
The library’s main header. Contains method
, virtual_ptr
and
virtual_ptr_traits
, use_classes
, the debug
and release
policies, etc.
<boost/openmethod/compiler.hpp>
Defines intialize
and finalize
, which are used to register classes and
<boost/openmethod/macros.hpp>
<boost/openmethod.hpp>
<boost/openmethod/policies.hpp>
<boost/openmethod/shared_ptr.hpp>
<boost/openmethod/unique_.hpp>
<boost/openmethod/with_vptr.hpp>
BOOST_OPENMETHOD
Synopsis
Defined in <boost/openmethod/macros.hpp>.
BOOST_OPENMETHOD(NAME, (PARAMETERS...), RETURN_TYPE [, POLICY]);
Description
Declares a method.
Effects
The macro expands to several constructs:
-
A
struct
forward declaration that acts as the method’s identifier:
struct BOOST_OPENMETHOD_NAME(NAME);
-
An inline function template, constrained to take the same
PARAMETERS
, without thevirtual_
decorators, returning aRETURN_TYPE
. The function forwards to
method<BOOST_OPENMETHOD_NAME(NAME)(PARAMETERS…), RETURN_TYPE, POLICY>::fn
. -
A guide function used to match overriders with the method:
auto BOOST_OPENMETHOD_NAME(NAME)_guide(...)
-> ::boost::openmethod::method<
BOOST_OPENMETHOD_NAME(NAME)(PARAMETERS...), RETURN_TYPE [, POLICY]>;
Note
|
The default value for POLICY is the value of
BOOST_OPENMETHOD_DEFAULT_POLICY at the point <boost/openmethod/core.hpp> is
included. Changing the value of this symbol has no effect after that point.
|
BOOST_OPENMETHOD_OVERRIDE
Synopsis
Defined in <boost/openmethod/macros.hpp>.
BOOST_OPENMETHOD_OVERRIDE(NAME, (PARAMETERS...), RETURN_TYPE) {
// body
}
Description
BOOST_OPENMETHOD_OVERRIDE
adds an overrider to a method.
The method is deduced from a call to a method guide function with the overrider’s arguments.
The macro creates several entities in the current scope.
-
A class template that acts as a container for the overriders of the methods called
NAME
:
template<typename...> BOOST_OPENMETHOD_OVERRIDERS(NAME);
-
A specialization of the container template for the overrider:
struct BOOST_OPENMETHOD_OVERRIDERS(NAME)<RETURN_TYPE(PARAMETERS...)> {
static auto fn(PARAMETERS...) -> RETURN_TYPE;
static auto has_next() -> bool;
template<typename... Args>
static auto next(typename... Args) -> RETURN_TYPE;
};
where:
-
fn
is the overrider function. -
has_next()
returnstrue
if a less specialized overrider exists. -
next(Args… args)
calls the next most specialized overrider via the pointer stored in the method’snext<fn>
member variable.
Finally, the macro starts the definition of the overrider function:
auto BOOST_OPENMETHOD_OVERRIDERS(NAME)<RETURN_TYPE(PARAMETERS...)>::fn(
PARAMETERS...) -> RETURN_TYPE
The block following the call to the macro is the body of the function.
BOOST_OPENMETHOD_INLINE_OVERRIDE
Synopsis
Defined in <boost/openmethod/macros.hpp>.
BOOST_OPENMETHOD_INLINE_OVERRIDE(NAME, (PARAMETERS...), RETURN_TYPE) {
// body
}
Description
BOOST_OPENMETHOD_INLINE_OVERRIDE
performs the same function as
BOOST_OPENMETHOD_OVERRIDE
, except that the overrider is defined inline.
BOOST_OPENMETHOD_REGISTER
Synopsis
Defined in <boost/openmethod/macros/register.hpp>.
BOOST_OPENMETHOD_REGISTER(TYPE);
Description
Creates a static instance of TYPE
, using a unique generated name.
BOOST_OPENMETHOD_CLASSES
Synopsis
Defined in <boost/openmethod/macros.hpp>.
BOOST_OPENMETHOD_CLASSES(CLASSES...[, POLICY]);
Description
Register CLASSES
in POLICY.
Note
|
The default value for POLICY is the value of
BOOST_OPENMETHOD_DEFAULT_POLICY when <boost/openmethod/core.hpp> is
included. Subsequently changing it has no retroactive effect.
|
This macro is a wrapper around use_classes
; see its documentation for more
details.
BOOST_OPENMETHOD_DEFAULT_POLICY
Description
The name of the default policy.
BOOST_OPENMETHOD_DEFAULT_POLICY
is the default value for the Policy
template
parameter of method
, use_classes
, and other constructs defined in
<boost/openmethod/core.hpp>
. If it is not defined,
::boost::openmethod::policy::default_policy
is used.
BOOST_OPENMETHOD_DEFAULT_POLICY
can be defined by a program to change the
default policy globally. Once <boost/openmethod/core.hpp>
has been included,
redefining the symbol has no effect. To override the default policy, proceed as
follows:
-
Include headers under
boost/openmethod/policies/
as needed. -
Create a policy class, and set
BOOST_OPENMETHOD_DEFAULT_POLICY
. -
Include
<boost/openmethod/core.hpp>
.
BOOST_OPENMETHOD_NAME
Synopsis
Defined in <boost/openmethod/macros/name.hpp>.
#define BOOST_OPENMETHOD_NAME(NAME) boost_openmethod_#NAME
Description
Generate a long name from a short name, hopefully avoiding conflicts with user-defined names.
BOOST_OPENMETHOD_OVERRIDERS
Synopsis
Defined in <boost/openmethod/macros.hpp>.
#define BOOST_OPENMETHOD_OVERRIDERS(NAME) \
BOOST_PP_CAT(BOOST_OPENMETHOD_NAME(NAME), _overriders)
Description
BOOST_OPENMETHOD_OVERRIDERS
expands to the name of the class template that
contains the overriders for all the methods with a given name.
initialize
Synopsis
Defined in <boost/openmethod/compiler.hpp>
.
namespace boost::openmethod {
template<class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
auto compiler<Policy>::initialize() -> /*unspecified*/;
}
Description
Initializes dispatch data for the methods registered in Policy
. This function
must be called before any calls to those methods, and after loading or unloading
a dynamic library that adds classes, methods or overriders to Policy
.
finalize
Synopsis
Defined in <boost/openmethod/compiler.hpp>
.
namespace boost::openmethod {
template<class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
auto finalize() -> void;
}
Description
De-allocates the resources allocated by initialize
for the Policy
, including
resources allocated by the facets in Policy
. Resources are de-allocated in an
arbitrary order. It is not necessary to call finalize
between calls to
initialize
. It may be required to cooperate with memory leak detection tools.
type_id
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>
.
namespace boost::openmethod {
using type_id = std::uintptr_t;
}
Description
type_id
is an unsigned integer type used to identify types. It is wide enough
to contain a pointer.
vptr_type
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>
.
namespace boost::openmethod {
using vptr_type = const /*unspecified*/ *;
}
Description
vptr_type
is the type of a pointer to a v-table.
method
Synopsis
namespace boost::openmethod {
template<
typename Method, typename ReturnType,
class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
class method;
template<typename Name, typename... Parameters, typename ReturnType, class Policy>
class method<Name(Parameters...), ReturnType, Policy> {
public:
using function_type = ReturnType (*)(CallParameters...);
auto operator()(CallParameters... args) const -> ReturnType;
static method fn;
template<auto... Functions>
struct override;
template<auto Overrider>
static function_type next;
private:
method();
method(const method&) = delete;
method(method&&) = delete;
~method();
};
}
Description
method
implements an open-method that takes a parameter list - Parameters
-
and returns a ReturnType
. Name
can be any type. Its purpose is to make it
possible to have multiple methods with the same signature. Typically, Name
is
a class whose name reflects the method’s purpose.
Parameters
must contain at least one virtual parameter, i.e. a parameter that
has a type in the form virtual_ptr<T, Policy>
or virtual_<T>
. The
dynamic types of the virtual arguments (the arguments corresponding to virtual
parameters in the method’s signature) are taken into account to select the
overrider to call.
A method
is attached to a Policy
, which influences several parts of the
dispatch mechanism - for example, how to obtain a v-table pointer for an object,
how to report errors, whether to perform sanity checks, etc.
Members
constructor
method();
Add the method to the list of methods registered in Policy
.
The constructor is private. The only instance is the static member variable
fn
.
destructor
~method();
Remove the method from the list of methods registered in Policy
.
operator()
auto operator()(CallParameters... args) const -> ReturnType;
Call the method with the arguments args
.
CallParameters
are the Parameters
without the virtual_
decorators. Note
that virtual_ptr
s are preserved.
The overrider is selected in a process similar to overloaded function resolution, with extra rules to handle ambiguities. It proceeds as follows:
-
Form the set of all applicable overriders. An overrider is applicable if it can be called with the arguments passed to the method.
-
If the set is empty, call the error handler (if present in the policy), then terminate the program with
abort
-
Remove the overriders that are dominated by other overriders in the set. Overrider A dominates overrider B if any of its virtual formal parameters is more specialized than B’s, and if none of B’s virtual parameters is more specialized than A’s.
-
If the resulting set contains only one overrider, call it.
-
If the return type is a registered polymorphic type, remove all the overriders that return a less specific type than the others.
-
If the resulting set contains only one overrider, call it.
-
Otherwise, call one of the remaining overriders. Which overrider is selected is not specified, but it is the same across calls with the same arguments types.
For each virtual argument arg
, the dispatch mechanism calls
virtual_traits::peek(arg)
and deduces the v-table pointer from the result
,
using the first of the following methods that applies:
-
If
result
is avirtual_ptr
, get the pointer to the v-table from it. -
If a function named
boost_openmethod_vptr
that takesresult
and returns avptr_type
exists, call it. -
Call
Policy::dynamic_vptr(result)
.
fn
static method fn;
The method
's unique instance. The method is called via the call
operator on fn
: method::fn(args…)
.
override
template<auto... Functions>
struct override;
Add Functions to the overriders of method
.
next
template<auto Overrider>
static function_type next;
Pointer to the next most specialized overrider after Overrider, i.e. the
overrider that would be called for the same tuple of virtual arguments if
Overrider was not present. Set to nullptr
if no such overrider exists.
method::override
Synopsis
namespace boost::openmethod {
template<typename Signature, typename ReturnType, class Policy>
template<auto... Functions>
struct method<Signature, ReturnType, Policy>::override {
override();
~override();
};
}
Usage:
method<Signature, ReturnType, Policy>::override<Functions...> some_unique_name;
// at file scope
Description
override
, instantiated as a static object, add one or more overriders to an
open-method.
Functions must fulfill the following requirements:
-
Have the same number of formal parameters as the method.
-
Each parameter in the same position as a
virtual_ptr<T>
in the method’s parameter list must be avirtual_ptr<U>
, where U is covariant with T. The Policy of thevirtual_ptr
s must be the same as the method’s Policy. -
Each formal parameter in the same position as a
virtual_
parameter must have a type that is covariant with the type of the method’s parameter. -
All other formal parameters must have the same type as the method’s corresponding parameters.
-
The return type of the overrider must be the same as the method’s return type or, if it is a polymorphic type, covariant with the method’s return type.
Members
constructor
override<Functions>::override();
Add Functions to the overriders of method
.
Destructor
override<Functions>::~method();
Remove Functions from the overriders of method
.
virtual_ptr
Synopsis
virtual_ptr
is defined in <boost/openmethod/core.hpp>
.
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
class virtual_ptr {
public:
static constexpr bool is_smart_ptr = /* see below */;
using element_type = /* see below */;
template<class Other> virtual_ptr(Other& other);
template<class Other> virtual_ptr(const Other& other);
template<class Other> virtual_ptr(Other&& other);
template<class Other>
static auto final(Other&& obj);
auto get() const -> element_type*;
auto operator->() const -> element_type*;
auto operator*() const -> element_type&;
auto pointer() const -> const Class*&;
template<typename Other>
auto cast() const -> virtual_ptr<Other, Policy>;
};
template<class Class>
virtual_ptr(Class&) -> virtual_ptr<Class, BOOST_OPENMETHOD_DEFAULT_POLICY>;
template<class Class>
inline auto final_virtual_ptr(Class& obj) -> virtual_ptr<
Class, BOOST_OPENMETHOD_DEFAULT_POLICY>;
template<class Policy, class Class>
inline auto final_virtual_ptr(Class& obj) -> virtual_ptr<Class, Policy>;
template<class Left, class Right, class Policy>
auto operator==(
const virtual_ptr<Left, Policy>& left,
const virtual_ptr<Right, Policy>& right) -> bool;
template<class Left, class Right, class Policy>
auto operator!=(
const virtual_ptr<Left, Policy>& left,
const virtual_ptr<Right, Policy>& right) -> bool;
} // namespace boost::openmethod
Defined in <boost/openmethod/shared_ptr.hpp>
:
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using shared_virtual_ptr = virtual_ptr<std::shared_ptr<Class>, Policy>;
template<
class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_shared_virtual(T&&... args)
-> shared_virtual_ptr<Class, Policy>;
}
Defined in <boost/openmethod/unique_ptr.hpp>
:
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using unique_virtual_ptr = virtual_ptr<std::unique_ptr<Class>, Policy>;
template<
class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_unique_virtual(T&&... args)
-> unique_virtual_ptr<Class, Policy>;
}
Description
virtual_ptr
is a wide pointer that combines a pointer to an object and a
pointer to its v-table. The object pointer can be a plain pointer or a smart
pointer. Specializations of virtual_traits
are required for smart pointers.
They are provided for std::unique_ptr
and std::shared_ptr
.
A plain virtual_ptr
can be constructed from a reference, a smart pointer, or
another virtual_ptr
. A smart virtual_ptr
can be constructed from a smart
pointer or from a smart virtual_ptr
. Usual conversions - from derived to base,
and from non-const to const - are allowed.
virtual_ptr
does not have a default constructor, nor a "null" state. In that
respect, it behaves more like a reference than a pointer. The only reason why it
is not called virtual_ref
is to save the name for the day C++ will support
smart references.
Members
is_smart_ptr
static constexpr bool is_smart_ptr;
true
if Class
is a smart pointer, false
otherwise. The value is derived
from virtual_traits<Class, Policy>
: if it has a member template called
rebind
, Class
is considered a smart pointer.
element_type
using element_type = std::conditional_t<
is_smart_ptr, typename Class::element_type, Class>;
The class of the object pointed to.
constructors
template<class Other> virtual_ptr(Other& other); // 1
template<class Other> virtual_ptr(const Other& other); // 2
template<class Other> virtual_ptr(Other&& other); // 3
(1), (2) If virtual_ptr
uses a plain pointer, other
must be a lvalue
reference to an object of a registered class, or to a virtual_ptr
(plain or
smart). If virtual_ptr
uses a smart pointer, other
must be a reference to a smart
pointer, or a smart virtual_ptr
.
(3) Smart virtual_ptr
only. Constructs a virtual_ptr
from a smart pointer or
a smart virtual_ptr
. The (smart) object pointer is moved from other
.
If other
is also a virtual_ptr
, the v-table pointer is copied from it.
Otherwise, it is deduced from the object. The Policy
must be the same for both
virtual_ptr
s.
final
template<class Other>
static auto final(Other&& obj);
Constructs a virtual_ptr
from a reference to an object, or from a smart
pointer. It is assumed that the static and dynamic types are the same. The
v-table pointer is initialized from the Policy::static_vptr
for the class,
which needs not be polymorphic.
get
auto get() const -> element_type*;
Returns a pointer to the object.
operator→
auto operator->() const -> element_type*;
Returns a pointer to the object.
operator*
auto operator*() const -> element_type&;
Returns a reference to the object.
pointer
auto pointer() const;
Returns a reference to the object pointer, which can be either a plain pointer or a smart pointer.
cast
template<typename Other>
auto cast() const -> virtual_ptr<Other, Policy>;
Returns a virtual_ptr
to the same object, cast to Other
.
Deduction guide
template<class Class>
virtual_ptr(Class&) -> virtual_ptr<Class, BOOST_OPENMETHOD_DEFAULT_POLICY>;
Non-members
virtual_shared_ptr
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using virtual_shared_ptr = virtual_ptr<std::shared_ptr<Class>, Policy>;
Convenience alias for virtual_ptr<std::shared_ptr<Class>, Policy>
.
virtual_unique_ptr
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using virtual_unique_ptr = virtual_ptr<std::unique_ptr<Class>, Policy>;
Convenience alias for virtual_ptr<std::unique_ptr<Class>, Policy>
.
final_virtual_ptr
template<class Policy, class Class>
inline auto final_virtual_ptr(Class&& obj);
template<class Class>
inline auto final_virtual_ptr(Class&& obj);
Utility functions, forwarding to virtual_ptr<Class, Policy>::final
.
If Policy
is not specified, BOOST_OPENMETHOD_DEFAULT_POLICY
is used.
make_shared_virtual
template<
class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_shared_virtual(T&&... args)
-> shared_virtual_ptr<Class, Policy>;
Creates an object using std::make_shared
and returns a virtual_shared_ptr
to
it. The v-table pointer is initialized from the the Policy::static_vptr
for
the class, which needs not be polymorphic.
make_unique_virtual
template<
class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_unique_virtual(T&&... args)
-> unique_virtual_ptr<Class, Policy>;
Creates an object using std::make_unique
and returns a virtual_unique_ptr
to
it. The v-table pointer is initialized from the the Policy::static_vptr
for
the class, which needs not be polymorphic.
operator==
template<class Left, class Right, class Policy>
auto operator==(
const virtual_ptr<Left, Policy>& left,
const virtual_ptr<Right, Policy>& right) -> bool;
Compares two virtual_ptr
objects for equality.
operator!=
template<class Left, class Right, class Policy>
auto operator!=(
const virtual_ptr<Left, Policy>& left,
const virtual_ptr<Right, Policy>& right) -> bool;
Compares two virtual_ptr
objects for inequality.
virtual_traits
Synopsis
Defined in <boost/openmethod/core.hpp>.
namespace boost::openmethod {
template<class, class>
struct virtual_traits; // not defined
template<class Class, class Policy>
struct virtual_traits<..., Policy> {
using virtual_type = ...;
static auto peek(const T& arg) -> const ...&;
template<typename Derived> static auto cast(T& obj) -> ...;
template<class Other> using rebind = ...; // for smart virtual pointers
};
}
Description
Specializations of virtual_traits
provide an interface for method
and
virtual_ptr
to manipulate virtual arguments.
Specializations
Specializations are provided for:
-
virtual_ptr<T, Policy>
-
const virtual_ptr<T, Policy>&
-
T&
-
T&&
-
std::shared_ptr<T>
: defined in <boost/openmethod/shared_ptr.hpp> -
const std::shared_ptr<T>&
: defined in <boost/openmethod/shared_ptr.hpp> -
std::unique_ptr<T>
: defined in <boost/openmethod/unique_ptr.hpp>
Members
virtual_type
using virtual_type = ...;
The class used for method selection. It must be registered in Policy.
For example, virtual_type
in the following specializations are all Class
:
-
virtual_traits<virtual_ptr<Class, Policy>>
-
virtual_traits<const virtual_ptr<std::shared_ptr<Class>&, Policy>
-
virtual_traits<Class&, Policy>
-
virtual_traits<const std::shared_ptr<Class>&, Policy>
peek
static auto peek(T arg) -> const ...&;
Returns a value for the purpose of obtaining a v-table pointer for arg
.
For example, peek
returns a const T&
for a T&
, a const T&
, a T&&
, and
a std::shared_ptr<T>
; and a const virtual_ptr<Class, Policy>&
for a
const virtual_ptr<Class, Policy>&
.
cast
template<typename Derived>
static decltype(auto) cast(T& obj);
Casts argument obj
to the type expected by an overrider.
For example, if a method takes a virtual_<Animal&>
, an overrider for Cat&
uses virtual_traits
to cast a Animal&
to a Cat&
.
rebind
template<class Other> using rebind = ...;
For smart pointers only. Rebinds the smart pointer to a different type. For
example, virtual_traits<std::shared_ptr<T>, Policy>::rebind<U>
is
std::shared_ptr<U>
.
use_classes
Synopsis
Defined in <boost/openmethod/core.hpp>.
namespace boost::openmethod {
template<class... Classes, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
struct use_classes {
use_classes();
~use_classes();
};
}
Usage:
use_classes<Classes...> some_unique_name; // at file scope
Description
use_classes
, instantiated as a static object, registers Classes
in Policy
.
Classes potentially involved in a method definition, an overrider, or a method
call must be registered via use_classes
. A class may be registered multiple
times. A class and its direct bases must be listed together in one or more
instantiations of use_classes
.
Virtual and multiple inheritance are supported, as long as they don’t result in a class lattice that contains repeated inheritance.
Note
|
The default value for Policy is the value of
BOOST_OPENMETHOD_DEFAULT_POLICY when <boost/openmethod/core.hpp> is
included. Subsequently changing it has no retroactive effect.
|
Members
constructor
use_classes();
Registers Classes
and their inheritance relationships in Policy
.
destructor
~use_classes();
Removes Classes
and their inheritance relationships from Policy
.
virtual_
Synopsis
Defined in <boost/openmethod/core.hpp>
.
namespace boost::openmethod {
template<typename T>
struct virtual_;
}
Description
Marks a formal parameter of a method as virtual. Requires a specialization of
virtual_traits
for T
and the Policy
of the method. Specializations for
T&
, T&&
, std::unique_ptr<T>
, std::shared_ptr<T>
and const
std::shared_ptr<T>&
are provided. See the documentation of virtual_traits
for
more information.
with_vptr
Synopsis
Defined in <boost/openmethod/core.hpp>.
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
class with_vptr {
protected:
with_vptr();
~with_vptr();
friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
};
template<class Class, class Base, class... MoreBases>
class with_vptr {
protected:
with_vptr();
~with_vptr();
friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
// if sizeof(MoreBases...) > 0
};
} // namespace boost::openmethod
Description
with_vptr
is a CRTP class template that embeds and manages a vptr across a
class hierarchy.
If Class
has no Bases
, with_vptr
adds a boost_openmethod_vptr
private
member to Class
. In either case, it sets the vptr to the v-table of Class
from Policy
. It also creates a boost_openmethod_vptr
friend function that
takes a a const Class&
and returns the embedded vptr.
If Class
has has more than one base, the boost_openmethod_vptr
friend
function is also created. It returns one of the embedded vptrs (it doesn’t
matter which one, as they all have the same value). This is to resolve
ambiguities
As part of its implementation, with_vptr
may also declare one or two free
functions (boost_openmethod_policy
and boost_openmethod_bases
) at certain
levels of the hierarchy.
Members
constructor
with_vptr();
Sets the vptr to the v-table for Class, obtained from Policy
. If Policy
contains indirect_vptr
, an additional level of indirection is added, thus
preserving the validity of the pointer across calls to initialize
.
destructor
~with_vptr();
For each Base
, sets the vptr to the v-table for that base.
Free Functions
auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
Returns the vptr embedded in obj
.
abstract_policy
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct abstract_policy {};
}
Description
abstract_policy
is a required base class for a policy. It makes it possible
for meta-functions such as use_classes
to discriminate between user classes
and the (optional) policy class.
domain
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
template<class Policy>
struct domain {
template<class Class> static vptr_type static_vptr;
// unspecified members
};
}
Description
domain
is a registry of classes and methods registered in a Policy,
and their dispatch tables.
Members
static_vptr
template<class Class>
static vptr_type static_vptr;
Contains the pointer to the v-table for Class. Set by initialize
.
basic_policy
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod {
namespace policies {
template<class Policy, class... Facets>
struct basic_policy : abstract_policy, domain<Policy>, Facets... {
template<class Facet>
static constexpr bool has_facet = /*unspecified*/;
template<class NewPolicy>
using fork = /*unspecified*/;
template<class... MoreFacets>
using add = /*unspecified*/;
template<class Base, class Facet>
using replace = /*unspecified*/;
template<class Base>
using remove = /*unspecified*/;
};
struct release : basic_policy<release, ...> {};
struct debug : release::add<...> {};
} // policies
#ifdef NDEBUG
using default_policy = policies::release;
#else
using default_policy = policies::debug;
#endif
} // boost::openmethod
Description
basic_policy
implements a policy, which consists of a a collection of methods,
classes, dispatch data, and facets, which specify how to obtain a pointer to a
v-table from an object, how to report errors, whether to perform runtime sanity
checks, etc.
Some of these functionalities require static variables local to the policy.
Forthis reason, basic_policy
uses the CRTP pattern to provide ensure that two
different policies - and the facets they contain - get their own copies of the
static state.
Members
has_facet
template<class Facet>
static constexpr bool has_facet;
Evaluates to true
if Policy contains Facet.
fork
template<class NewPolicy>
using fork;
Creates a new policy from an existing one. NewPolicy, and the facets it contains, do not share static variables with the original Policy. The new policy does not retain any knowledge of the classes and methods registered in the original.
add
template<class... MoreFacets>
using add;
Creates a new policy by adding MoreFacets to the original policy’s collection of facets. The original policy and the new one share static variables.
replace
template<class Base, class NewFacet>
using replace;
Creates a new policy by replacing the facet in Policy that derives from Base with NewFacet. It is not an error if policy does not contain such a facet; in that case, the new policy contains the same facet as the original one.
The original policy and the new one share static variables.
remove
template<class Base>
using remove;
Creates a new policy by removing the facet in Policy that derives from Base. It is not an error if policy does not contain such a facet; in that case, the new policy contains the same facet as the original one.
The original policy and the new one share static variables.
Non-members
release
struct release;
A policy that contains facet implementations std_rtti
, fast_perfect_hash
,
vptr_vector
and vectored_error_handler
.
debug
struct debug;
The release
policy with additional facet implementations runtime_checks
,
basic_error_output
and basic_trace_output.
Note
|
debug extends release but it does not a fork it. Both policies use the
same domain .
|
default_policy
An alias for release
if NDEBUG
is defined, and for debug
otherwise.
facet
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct facet {
static auto finalize() -> void;
};
} // boost::openmethod::policies
Description
facet
is the base class of all facets. It provides an empty finalize
static
function which can be overriden (via shadowing) by derived classes.
Members
finalize
static auto finalize() -> void;
Does nothing.
rtti
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct rtti : facet {};
} // boost::openmethod::policies
Description
The rtti
facet provides type information for classes and objects, implements
downcast in presence of virtual inheritance, and writes descriptions of types to
an ostream
-like object.
Requirements
static_type
template<class Class>
static auto static_type() -> type_id;
Returns a type_id
for Class
.
dynamic_type
template<class Class>
static auto dynamic_type(const Class& obj) -> type_id;
Returns a type_id
for an object’s dynamic type.
type_name
template<typename Stream>
static auto type_name(type_id type, Stream& stream) -> void;
Writes a description of type
to stream
.
This requirement is optional. rtti
provides a default implementation that writes typeid({type})
to stream
.
type_index
static auto type_index(type_id type) -> /* unspecified */;
Returns a unique key for type
. Required only for RTTI systems that assign more
than one type "identifiers" to a type. For example, standard RTTI allows
implementations to have multiple instances of std::type_info
for the same
type.
dynamic_cast_ref
template<typename D, typename B>
static auto dynamic_cast_ref(B&& obj) -> D;
Casts obj
to D
. Required only if using virtual inheritance.
std_rtti
Synopsis
Defined in <boost/openmethod/policies/std_rtti.hpp>.
namespace boost::openmethod::policies {
struct std_rtti : rtti {
template<class Class>
static auto static_type() -> type_id;
template<class Class>
static auto dynamic_type(const Class& obj) -> type_id;
template<typename Stream>
static auto type_name(type_id type, Stream& stream) -> void;
static auto type_index(type_id type) -> std::type_index;
template<typename D, typename B>
static auto dynamic_cast_ref(B&& obj) -> D;
};
} // boost::openmethod::policies
Description
std_rtti
is an implementation of the rtti
facet that uses standard RTTI.
Members
static_type
template<class Class>
static auto static_type() -> type_id;
Return the address of Class’s `type_info
, cast to a type_id
.
dynamic_type
template<class Class>
static auto dynamic_type(const Class& obj) -> type_id;
Return the address of obj
's type_info
, cast to a type_id
.
type_name
template<typename Stream>
static auto type_name(type_id type, Stream& stream) -> void;
Write the demangled name of the class identified by type
to stream
.
Execute stream << reinterpret_cast<const std::type_info*>(type)→name()
.
type_index
static auto type_index(type_id type) -> /*unspecified*/;
Return std::type_index(reinterpret_cast<const std::type_info>(type))
.
The function is required because C++ does not guarantee that there is a single
instance of std::type_info
for each specific type.
dynamic_cast_ref
template<typename Derived, typename Base>
static auto dynamic_cast_ref(Base&& obj) -> Derived;
Cast obj
using the dynamic_cast
operator.
deferred_static_rtti
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct deferred_static_rtti : rtti {};
}
Description
deferred_static_rtti
is a facet that defers collection of static type ids.
Some custom RTTI systems rely on static constructors to assign type ids.
OpenMethod itself relies on static constructors to register classes, methods and
overriders, calling the static_type
function from the rtti
facet in the
process. This can result in collecting the type ids before they have been
initialized. Adding this facet to a policy moves the collection of type ids to
initialize
.
minimal_rtti
Synopsis
struct minimal_rtti : rtti {
template<typename Class>
static auto static_type() -> type_id;
};
Description
minimal_rtti
is an implementation of the rtti
facet that only uses static
type information.
minimal_rtti
provides the only function strictly required for the rtti
facet.
This facet can be used in programs that call methods solely via
virtual_ptr
s created with the "final" constructs. Virtual inheritance
is not supported. Classes are not required to be polymorphic.
Members
static_type
template<class Class>
static auto static_type() -> type_id;
Returns the address of a local static char
variable, cast to type_id
.
extern_vptr
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct extern_vptr : facet {};
}
Description
extern_vptr
is a facet that stores and returns pointers to v-tables for
registered classes.
Requirements
register_vptrs
template<typename ForwardIterator>
auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;
ForwardIterator
is a forward iterator over a range of objects that contain
information about the type ids and the vptr of a registered class. They have the
following member functions:
auto type_id_begin() const -> type_id_forward_iterator;
auto type_id_end() const -> type_id_forward_iterator;
auto vptr() const -> const vptr_type&;
type_id_begin
and type_id_end
return iterators delimiting a range of
`type_id`s for a class.
vptr
returns a reference to a static variable containing a pointer to the
v-table for a the class. Its value is set by initialize
. While the value of
the variable changes with each call to initialize
, the variable itself remains
the same.
indirect_vptr
Synopsis
struct indirect_vptr : facet {};
Description
indirect_vptr
is a facet that makes virtual_ptr
s and with_vptr
use
pointers to pointers to v-tables, instead of straight pointers. As a
consequence, they remain valid after a call to initialize
.
Requirements
None. The facet is its own implementation.
vptr_vector
Synopsis
Defined in <boost/openmethod/policies/vptr_vector.hpp>.
namespace boost::openmethod::policies {
template<class Policy, typename Facet = void>
class vptr_vector : Base {
public:
template<typename ForwardIterator>
static auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;
template<class Class>
static auto dynamic_vptr(const Class& arg) -> const vptr_type&;
};
}
Description
vptr_vector
is an implementation or external_vptr
that keeps the pointers
to the v-tables in a std::vector
. If UseIndirectVptrs
is indirect_vptr
,
stores pointers to pointers to the v-tables.
If Facet
is specified, it must be either void
or indirect_vptr
. void
indicates that straight pointers to v-tables should be stored.
Policy
is the policy containing the facet.
Members
register_vptrs
template<typename ForwardIterator>
auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;
Stores the pointers to v-tables in a vector, indexed by the (possibly hashed)
type_id`s of the classes registered in `Policy
.
If Policy
contains a type_hash
facet, call its hash_initialize
function, and uses it to convert the type_id
s to an index.
dynamic_vptr
template<class Class>
auto dynamic_vptr(const Class& object) -> const vptr_type&;
Returns a pointer to the v-table for object
(by reference).
Obtains a type_id
for object
using Policy::dynamic_type
. If Policy
contains a type_hash
facet, uses it to convert the result to an index;
otherwise, uses the type_id
as the index.
vptr_map
Synopsis
namespace boost::openmethod::policies {
### Synopsis
template<class Policy, class Facet = void, class Map = /* unspecified */>
class vptr_map : extern_vptr, Facet /* if not void */ {
public:
template<typename ForwardIterator>
static auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;
template<class Class>
static auto dynamic_vptr(const Class& arg) -> const vptr_type&;
};
}
Description
vptr_map
is an implementation of `external_vptr that stores the pointers to
the v-tables in a map.
Policy
is the policy containing the facet.
If Facet
is specified, it must be either void
or indirect_vptr
. void
indicates that straight pointers to v-tables should be stored.
Map
is an AssociativeContainer
that maps type_id
s to
vptr_type
s if Facet
is void
, or pointers vptr_type
s if
Facet
is indirect_vptr
.
Members
register_vptrs
template<typename ForwardIterator>
auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;
Stores the pointers to v-tables in a Map.
dynamic_vptr
template<class Class>
auto dynamic_vptr(const Class& object) -> const vptr_type&;
Returns a pointer to the v-table for object
(by reference).
If Policy contains the runtime_checks
facet, checks if Class is
registered. If it is not, and Policy contains a error_handler
facet, calls
its error
function; then calls abort
.
type_hash
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct type_hash : facet {};
} // boost::openmethod::policies
Description
type_hash
is a facet that provides a hash function for a fixed set of
type_id
s.
Requirements
hash_type_id
static auto hash_type_id(type_id type) -> type_id;
Returns the hash of type
.
hash_initialize
template<typename ForwardIterator>
static auto hash_initialize(ForwardIterator first, ForwardIterator last)
-> Report;
Finds a hash function for the type_id
s in the range [first, last)
.
ForwardIterator
is the same as in vptr_vector::register_vptrs
.
hash_initialize
returns a Report
object which is required to have two
members, first
and last
, which define the range [first, last)
of the
possible output values of the hash function.
fast_perfect_hash
Synopsis
Defined in <boost/openmethod/policies/fast_perfect_hash.hpp>.
class fast_perfect_hash : type_hash
{
public:
static auto hash_type_id(type_id type) -> type_id;
template<typename ForwardIterator>
static auto hash_initialize(ForwardIterator first, ForwardIterator last) -> Report;
};
Description
fast_perfect_hash
implements a very fast, perfect (but not minimal) hash
function for type_id
s.
Members
Find two factors
hash_type_id
static auto hash_type_id(type_id type) -> type_id;
Returns (type * M) >> S
, where M
and S
are factors found by
hash_initialize
.
If the policy has a runtime_checks
facet, hash_type_id
checks that type
corresponds to a registered class. If not, it reports a unknown_class_error
using the policy’s error_handler facet, if present, then calls abort
.
hash_initialize
template<typename ForwardIterator>
auto hash_initialize(ForwardIterator first, ForwardIterator last) -> Report;
Finds factors M
and S
such that hash_type_id
is a collision-free hash
function.
If no such factors cannot be found, hash_initialize
reports a
hash_search_error
using the policy’s error_handler facet, if present, the
calls abort
.
If the policy has a trace_output
facet, hash_initialize
uses it to write a
summary of the search.
error_handler
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct error_handler;
}
Defined in <boost/openmethod/types.hpp>.
namespace boost::openmethod {
struct openmethod_error {};
struct not_implemented_error : openmethod_error {
type_id method;
std::size_t arity;
static constexpr std::size_t max_types = 16;
type_id types[max_types];
};
struct unknown_class_error : openmethod_error {
type_id type;
};
struct hash_search_error : openmethod_error {
std::size_t attempts;
std::size_t buckets;
};
struct type_mismatch_error : openmethod_error {
type_id type;
};
} // boost::openmethod::policies
Description
error_handler
is a facet that handles errors.
When an error is encountered, either during initialize
or method dispatch, the
program is terminated via a call to abort
. If this facet is present in the
policy, its error
function is called with an error object. It can prevent
termination by throwing an exception.
Requirements
Implementations of error_handler
must provide the following functions:
error
static auto error(const T&) -> void;
vectored_error_handler
Synopsis
Defined in <boost/openmethod/policies/vectored_error_handler.hpp>.
namespace boost::openmethod::policies {
template<class Policy>
class vectored_error_handler : public error_handler {
public:
using error_variant = std::variant<
openmethod_error, not_implemented_error, unknown_class_error,
hash_search_error, type_mismatch_error, static_slot_error,
static_stride_error>;
using function_type = std::function<void(const error_variant& error)>;
template<class Error>
static auto error(const Error& error) -> void;
static auto set_error_handler(error_handler_type handler) -> function_type;
};
}
Description
vectored_error_handler
is an implementation of error_handler
that calls a
std::function
to handle the error.
Members
error
template<class Error>
static auto error(const Error& error) -> void;
Calls the function last set via set_error_handler
or, if it was never called,
and if Policy contains an error_output
facet, use it to print a description
of error
.
error
static auto set_error_handler(function_type handler) -> function_type;
Sets handler
as the function to call in case of error.
throw_error_handler
Synopsis
Defined in <boost/openmethod/policies/throw_error_handler.hpp>.
namespace boost::openmethod::policies {
struct throw_error_handler : error_handler {
template<class Error>
[[noreturn]] static auto error(const Error& error) -> void;
};
} // boost::openmethod::policies
Description
throw_error_handler is an implementation of the error_handler
facet that
throws the error as an exception.
Members
error
template<class Error>
[[noreturn]] static auto error(const Error& error) -> void;
Throws error
.
error_output
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct error_output : facet {};
} // boost::openmethod::policies
Description
error_output
is a facet that provides a stream for writing error messages.
Requirements
error_stream
static RestrictedOutputStream error_stream;
A static variable that satisfies the requirements of RestrictedOutputStream
.
basic_error_output
Synopsis
Defined in <boost/openmethod/policies/basic_error_output.hpp>.
namespace boost::openmethod::policies {
template<class Policy, typename Stream = /*unspecified*/>
struct basic_error_output : error_output {
static Stream error_stream;
};
}
Description
basic_error_output
is an implementation of error_output
that writes error
messages to a RestrictedOutputStream
.
Members
error_stream
Stream error_stream;
Initialized by the default constructor of Stream
. It is the responsibility of
the program to initializate it if needed, e.g., for a std::ofstream
, to open
it.
trace_output
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct trace_output : facet {};
}
Description
trace_output
is a facet used to write trace messages.
initialize
can be directed to describe the classes and methods in a policy,
and how the dispatch tables are built, by including this facet in the policy,
and setting trace_enabled
to true
. The content and the format of the
description is not documented, beyond the guarantee that it provides an
exhaustive account of table construction, and may change between major, minor
and patch versions.
Requirements
trace_enabled
static bool trace_enabled;
true
if tracing is enabled, false
otherwise.
trace_stream
static RestrictedOutputStream trace_stream;
A static variable that satisfies the requirements of RestrictedOutputStream
.
basic_trace_output
Synopsis
Defined in <boost/openmethod/policies/basic_trace_output.hpp>.
namespace boost::openmethod::policies {
template<class Policy, typename Stream = /*unspecified*/>
struct basic_trace_output : trace_output {
static bool trace_enabled;
static Stream trace_stream;
};
}
Description
basic_error_output
is an implementation of trace_output
that writes error
messages to a RestrictedOutputStream
.
Members
trace_enabled
static bool trace_enabled;
Set to true
if environment variable BOOST_OPENMETHOD_TRACE
is set to 1
.
trace_stream
static Stream trace_stream;
Initialized by the default constructor of Stream
. It is the responsibility of
the program to prepare it for output if needed, e.g., for a std::ofstream
, to
open it.
RestrictedOutputStream
Description
RestrictedOutputStream is a concept describing a std::ostream
-like class with
a reduced set of operations.
While convenient, std::ostream
and its implementations constitute a sizeable
piece of code, which may make it unsuitable for certain applications. OpenMethod
uses a small subset of the operations supported by std::ostream
. By default,
the library uses a lightweight implementation based on the C stream functions.
Implementations of RestrictedOutputStream
provide the following functions:
Name | Description |
---|---|
RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const char* str) |
Write a null-terminated string |
RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const std::string_view& view) |
Write a view to `os |
RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const void* value) |
Write a representation of a pointer to |
RestrictedOutputStream& operator<<(RestrictedOutputStream& os, std::size_t value) |
Write an unsigned integer to |