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?
Open-methods also address the banana-gorilla-shared problem:
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire shared. — Joe Armstrong, creator of Erlang progamming language
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>
using boost::openmethod::virtual_ptr;
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";
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.
Headers and Namespaces
Most real-life programs will be organized in multiple files and multiple namespaces. OpenMethod interacts with headers and namespaces naturally, if using-directives are avoided. In that case, there are a few things to be aware of.
Let’s break the Animals example into headers and namespaces. First we put
Animal in its own header and namespace:
// animal.hpp
#ifndef ANIMAL_HPP
#define ANIMAL_HPP
#include <boost/openmethod.hpp>
#include <string>
namespace animals {
struct Animal {
Animal(std::string name) : name(name) {
}
std::string name;
virtual ~Animal() = default;
};
BOOST_OPENMETHOD(
poke, (std::ostream&, boost::openmethod::virtual_ptr<Animal>), void);
} // namespace animals
#endif // ANIMAL_HPP
BOOST_OPENMETHOD can be placed in a header file. It adds several constructs to
the current namespace:
-
It declares (but does not define) a
structnamed after the method. -
It declares (but does not define) a guide function. It is also named after the method, and it has the same signature (with the
virtual_decorators stripped). It is used to match methods and overriders. It is never defined and it is "called" only in a non-evaluated context. -
It defines an inline function with the same name and signature as the method (with the
virtual_decorators stripped).
Next, let’s implement the Cat class, and a derived class, Cheetah, in the
felines namespace:
// felines.hpp
#ifndef FELINES_HPP
#define FELINES_HPP
#include "animal.hpp"
namespace felines {
struct Cat : animals::Animal {
using Animal::Animal;
};
struct Cheetah : Cat {
using Cat::Cat;
};
} // namespace felines
#endif // FELINES_HPP
// cat.cpp
#include <iostream>
#include <boost/openmethod.hpp>
#include "cat.hpp"
using boost::openmethod::virtual_ptr;
namespace felines {
BOOST_OPENMETHOD_CLASSES(animals::Animal, Cat, Cheetah);
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Cat> cat), void) {
os << cat->name << " hisses";
}
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Cheetah> cat), void) {
BOOST_OPENMETHOD_OVERRIDER(
poke, (std::ostream & os, virtual_ptr<Cat> dog), void)::fn(os, cat);
os << " and runs away";
}
} // namespace felines
BOOST_OPENMETHOD_CLASSES should be placed in an implementation file. It can
also go in a header file, but this wastes space, as the same registrar will be
created in every translation unit that includes the header. It doesn’t matter
which namespace the macro is called in. It can take be used with any class name
in scope, or with qualified names.
BOOST_OPENMETHOD_OVERRIDE uses the guide function declared by
BOOST_OPENMETHOD to locate a method that can be called with the same arguments
as the overrider itself. It "calls" the guide function in a non-evaluated
context, passing it a std::ostream& and a virtual_ptr<Cat>. The return type
of the guide function is the method to add the overrider to. Exactly one guide
function must match. The normal rules of overload resolution apply. In that
case, the guide function is found via argument dependant lookup (ADL).
The macro adds several constructs to the current namespace:
-
It declares (but does not define) a
structtemplate with one type parameter, named after the method. The template acts like a container for overriders. -
It specializes the template for the signature of the overrider. Inside the struct, it defines the
nextandhas_nextmembers, and a static function calledfn. The block following the macro is the body of thefnfunction.
It follows that BOOST_OPENMETHOD_OVERRIDE should be placed in an
implementation file. BOOST_OPENMETHOD_INLINE_OVERRIDE works like
BOOST_OPENMETHOD_OVERRIDE, but it defines the fn function as inline, so it
can be used in a header file.
The overrider for Cats can be accessed in the same translation unit, after it
has been defined, using the BOOST_OPENMETHOD_OVERRIDER macro. It expands to
the specialization of the overrider container for the overrider’s signature. We
call the static fn function to call the overrider.
|
Note
|
The Cheetah overrider calls the specific overrider for Cat, for
illustration purpose. It is usually better to call next instead.
|
Let’s implement the Dog class, in the canines namespace. This time we want
the overrider to be accessible in other translation units. We can declare an
overrider with BOOST_OPENMETHOD_DECLARE_OVERRIDER, without actually defining
the static function fn just yet.
#ifndef CANINES_HPP
#define CANINES_HPP
#include <iosfwd>
#include <boost/openmethod.hpp>
#include "animal.hpp"
namespace canines {
struct Dog : animals::Animal {
using Animal::Animal;
};
BOOST_OPENMETHOD_DECLARE_OVERRIDER(
poke, (std::ostream & os, boost::openmethod::virtual_ptr<Dog> dog), void);
} // namespace canines
#endif // CANINES_HPP
Unlike function declarations, which can occur multiple times in a TU, an overrider declaration cannot. For example, this is illegal:
BOOST_OPENMETHOD_DECLARE_OVERRIDER(
poke, (std::ostream&, virtual_ptr<Dog>), void);
BOOST_OPENMETHOD_DECLARE_OVERRIDER(
poke, (std::ostream&, virtual_ptr<Dog>), void);
Now we use BOOST_OPENMETHOD_DEFINE_OVERRIDER to define the overrider:
#include <iostream>
#include <boost/openmethod.hpp>
#include "dog.hpp"
namespace canines {
BOOST_OPENMETHOD_CLASSES(animals::Animal, Dog);
BOOST_OPENMETHOD_DEFINE_OVERRIDER(
poke, (std::ostream & os, boost::openmethod::virtual_ptr<Dog> dog), void) {
os << dog->name << " barks";
}
} // namespace canines
Let’s look at the main program now. It derived Bulldog from Dog and provides
an overrider for the new class:
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
#include "animal.hpp"
#include "cat.hpp"
#include "dog.hpp"
using boost::openmethod::virtual_ptr;
struct Bulldog : canines::Dog {
using Dog::Dog;
};
BOOST_OPENMETHOD_CLASSES(canines::Dog, Bulldog);
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Bulldog> dog), void) {
canines::BOOST_OPENMETHOD_OVERRIDER(
poke, (std::ostream & os, virtual_ptr<canines::Dog> dog),
void)::fn(os, dog);
os << " and bites back";
}
auto main() -> int {
boost::openmethod::initialize();
std::unique_ptr<animals::Animal> felix(new felines::Cat("Felix"));
std::unique_ptr<animals::Animal> azaad(new felines::Cheetah("Azaad"));
std::unique_ptr<animals::Animal> snoopy(new canines::Dog("Snoopy"));
std::unique_ptr<animals::Animal> hector(new Bulldog("Hector"));
poke(std::cout, *felix); // Felix hisses
std::cout << ".\n";
poke(std::cout, *azaad); // Azaad hisses and runs away
std::cout << ".\n";
poke(std::cout, *snoopy); // Snoopy barks
std::cout << ".\n";
poke(std::cout, *hector); // Hector barks and bites
std::cout << ".\n";
return 0;
}
Again ADL plays a role: it helps the overrider (and main) to locate the poke
method.
This example is the "happy scenario", where namespaces are used conservatively.
The OVERRIDE macros don’t interact well with using directives. For example
this code:
using namespace animals;
using namespace canines;
using namespace felines;
struct Bulldog : Dog {
using Dog::Dog;
};
BOOST_OPENMETHOD_CLASSES(Dog, Bulldog);
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Bulldog> dog), void) {
next(os, dog);
os << " and bites back";
}
…will fail to compile, with an error like "reference to 'poke_boost_openmethod_overriders' is ambiguous". That is because the overrider containers exist in both the canines and felines namespaces, with the same name.
Finally, the names passed as first arguments to the BOOST_OPENMETHOD and BOOST_OPENMETHOD_OVERRIDE macros must be identifiers. Qualified names are not allowed. Consider:
using animals::Animal;
namespace app_specific_behavior {
BOOST_OPENMETHOD(
meet, (std::ostream&, virtual_ptr<Animal>, virtual_ptr<Animal>), void);
} // namespace app_specific_behavior
BOOST_OPENMETHOD_OVERRIDE(
meet, (std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
os << "ignore";
}
Here, the guide function cannot be found, even via ADL. We get an error like
"use of undeclared identifier 'meet_boost_openmethod_guide'". How do we solve
this? We might be tempted to use a qualified name:
app_specific_behavior::meet:
BOOST_OPENMETHOD_OVERRIDE(
app_specific_behavior::meet,
(std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
os << "ignore";
}
But BOOST_OPENMETHOD_OVERRIDE also uses the name to derive the overrider
container’s name, using preprocessor token pasting, resulting in an invalid
declaration error.
We need to do is to make BOOST_OPENMETHOD_OVERRIDE "see" the guide function.
Its name is returned by macro BOOST_OPENMETHOD_GUIDE(NAME). We can use a
using-declaration to bring the guide function into the current scope:
using app_specific_behavior::BOOST_OPENMETHOD_GUIDE(meet);
BOOST_OPENMETHOD_OVERRIDE(
meet, (std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
os << "ignore";
}
Friendship
We can use overrider containers to grant friendship to a specific overrider, or
to all the overriders of a method. The name of the container template is
returned by BOOST_OPENMETHOD_OVERRIDERS. 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 - with any
signature - are granted friendship.
We can also befriend 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 + mult]
imul rdx, qword ptr [rax - 8]
movzx ecx, byte ptr [rip + 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>
using boost::openmethod::virtual_ptr;
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/interop/std_unique_ptr.hpp>
#include <boost/openmethod/compiler.hpp>
using namespace boost::openmethod::aliases;
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_registry
(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, void*) {
return a.vptr;
}
public:
Animal() {
vptr = boost::openmethod::default_registry::static_vptr<Animal>;
}
};
class Cat : public Animal {
public:
Cat() {
vptr = boost::openmethod::default_registry::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 inplace_vptr CRTP class automates the creation and management of embedded
vptrs.
#include <boost/openmethod/inplace_vptr.hpp>
class Animal : public boost::openmethod::inplace_vptr<Animal> {
};
class Cat : public Animal, public boost::openmethod::inplace_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 inplace_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. inplace_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.hpp>
class BOOST_OPENMETHOD_ID(poke);
using poke = method<
BOOST_OPENMETHOD_ID(poke),
auto(std::ostream&, virtual_ptr<Animal>)->void>;
|
Note
|
BOOST_OPENMETHOD and associated macros use BOOST_OPENMETHOD_ID 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.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 policys. Each policy belongs to a policy category. A
policy may contain at most one policy of a given category. 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 policys in the same policy as part of their implementation. See the
reference for the list of policys. Policies and policys are placed in the
boost::openmethod::policies namespace. Two stock policies are provided by the
library: release and debug.
The release policy contains the following policys:
| policy category | policy | role |
|---|---|---|
rtti |
std_rtti |
provides type information for classes and objects |
extern_vptr |
vptr_vector |
stores vptrs in an indexed collection |
type_hash |
fast_perfect_hash |
hash type id to an index in a vector |
error_handler |
default_error_handler |
handles errors |
The debug policy contains the same policys as release, plus a few more:
| policy category | policy | role |
|---|---|---|
runtime_checks |
(itself) |
enables runtime checks |
output |
basic_error_output |
prints error descriptions to |
trace |
basic_trace_output |
enables |
Policies, and some policys, 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
constructed from existing policies by adding and removing policys. For example,
policies::debug is a tweak of policies::release:
namespace boost::openmethod::policies {
struct debug : release::fork<debug>::with<
runtime_checks, basic_error_output<debug>,
basic_trace_output<debug>> {};
}
boost::openmethod::default_registry 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_REGISTRY 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_REGISTRY
has no effect.
Error Handling
When an error is encountered, the program is terminated by a call to abort. If
the policy contains an error_handler policy, 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 policy with
default_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>
using boost::openmethod::virtual_ptr;
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_registry::error_handler::set([](const auto& 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 policy with our own. For example:
#include <iostream>
#include <boost/openmethod/default_registry.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 {
template<class Registry>
struct fn {
static auto error(const bom::openmethod_error&) -> void {
}
static auto error(const bom::not_implemented_error& err) -> void {
throw err;
}
};
};
struct custom_registry : bom::default_registry::with<throw_if_not_implemented> {
};
#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_registry
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
using boost::openmethod::virtual_ptr;
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 policy 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<class Class>
static constexpr auto is_polymorphic = std::is_polymorphic_v<Class>;
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);
}
};
-
is_polymorphicis used to check if a class is polymorphic. This template is required. -
static_typeis used by class registration, byvirtual_ptr's "final" constructs, and to format error and trace messages.Tis not restricted to the classes that appear as virtual parameters. This function is required. -
dynamic_typeis 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_vptris provided for all the classes in the policy, it can be omitted. -
type_namewrites a representation oftypetostream. It is used to format error and trace messages.Streamis a lighweight version ofstd::ostreamwith 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_indexreturns an object that uniquely identifies a class. Some forms of RTTI (most notably, C++'stypeidoperator) do not guarantee that the type information object for a class is unique within the same program. This function is optional; if not provided,typeis assumed to be unique, and used as is. -
dynamic_cast_refcastsobjto classD.B&&is either a lvalue reference (possibly cv-qualified) or a rvalue reference.Dhas 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 policy. We must not include
<boost/openmethod/core.hpp> or any header that includes it yet.
Here is the policy implementation:
namespace bom = boost::openmethod;
struct custom_rtti : bom::policies::rtti {
template<class Registry>
struct fn : bom::policies::rtti::fn<Registry> {
template<class T>
static constexpr bool is_polymorphic = std::is_base_of_v<Animal, T>;
template<typename T>
static auto static_type() -> bom::type_id {
if constexpr (is_polymorphic<T>) {
return reinterpret_cast<bom::type_id>(T::static_type);
} else {
return nullptr;
}
}
template<typename T>
static auto dynamic_type(const T& obj) -> bom::type_id {
if constexpr (is_polymorphic<T>) {
return reinterpret_cast<bom::type_id>(obj.type);
} else {
return nullptr;
}
}
};
};
This policy 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::registry<custom_rtti, bom::policies::vptr_vector> {
};
#define BOOST_OPENMETHOD_DEFAULT_REGISTRY custom_policy
Next, we include the main header. Because BOOST_OPENMETHOD_DEFAULT_REGISTRY is
defined, its value is used for the default policy. Then comes the usual example.
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
using boost::openmethod::virtual_ptr;
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 works 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 policy 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 policy 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 policy - deferred_static_rtti:
struct custom_policy
: bom::policies::basic_policy<
custom_policy, custom_rtti,
bom::policies::deferred_static_rtti, // <-- additional policy
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 inplace_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 policy. 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 : boost::openmethod::default_registry::with<
boost::openmethod::policies::indirect_vptr> {};
template<class Class>
using dyn_vptr = boost::openmethod::virtual_ptr<Class, dynamic>;
BOOST_OPENMETHOD(
encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string,
dynamic);
|
Note
|
The policy must be passed to the method as well as the
virtual_ptrs.
|
The indirect_vptr policy tells virtual_ptr to use a pointer to the vptr. 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/interop/std_unique_ptr.hpp>
#include <boost/openmethod/compiler.hpp>
#include "dl.hpp"
BOOST_OPENMETHOD_CLASSES(
Animal, Herbivore, Cow, Wolf, Carnivore, dynamic);
BOOST_OPENMETHOD_OVERRIDE(
encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string) {
return "ignore\n";
}
At this point we only have one overrider. Animals of all species ignore one another:
auto main() -> int {
using namespace boost::openmethod;
initialize<dynamic>();
std::cout << "Before loading library\n";
auto gracie = make_unique_virtual<Cow, dynamic>();
// Wolf _willy;
// auto willy = virtual_ptr<Wolf, dynamic>(_willy);
auto willy = make_unique_virtual<Wolf, dynamic>();
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);
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>();
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>();
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 default_registry, etc.
boost::openmethod::policies
Contains the policy framework.
Headers
<boost/openmethod/core.hpp>
The library’s main header. Provides method, virtual_ptr and
virtual_ptr_traits, use_classes, the default policy, etc.
If BOOST_OPENMETHOD_DEFAULT_REGISTRY is defined before including this header,
its value is used as the default value for the Policy template parameter
throughout the code. Otherwise, boost::openmethod::default_registry is used.
Setting BOOST_OPENMETHOD_DEFAULT_REGISTRY after including the core header has no
effect.
<boost/openmethod/macros.hpp>
Provides BOOST_REGISTER_CLASSES, BOOST_OPENMETHOD,
BOOST_OPENMETHOD_OVERRIDE and other macros.
<boost/openmethod.hpp>
Convenience header. Includes <boost/openmethod/core.hpp> and
<boost/openmethod/macros.hpp>.
Also imports boost::openmethod::virtual_ptr in the global namespace. This is
usually regarded as bad practice. The rationale is that OpenMethod emulates a
language feature, and virtual_ptr is equivalent to keyword, similar to
virtual. Besides, the macros are global as well.
There are two ways to avoid importing virtual_ptr while still using the
macros:
-
Define
BOOST_OPENMETHOD_NO_GLOBAL_VIRTUAL_PTRbefore including<boost/openmethod.hpp>. This disables the import ofvirtual_ptrin the global namespace. -
Include
<boost/openmethod/core.hpp>`and `<boost/openmethod/macros.hpp>.
<boost/openmethod/compiler.hpp>
Provides intialize and finalize. Typically included only by the translation
unit that contains main, unless dynamic loading is used in other places in the
program.
<boost/openmethod/interop/std_shared_ptr.hpp>
Provides support for using std::shared_ptr in place of plain pointers in
virtual parameters.
<boost/openmethod/unique_.hpp>
Provides support for using std::unique_ptr in place of plain pointers in
virtual parameters.
<boost/openmethod/inplace_vptr.hpp>
Provides support for storing v-table pointers directly in objects, in the same manner as native virtual functions.
<boost/openmethod/policies.hpp>
Provides the debug and release policies in the boost::openmethod::policies
namespace, and default_registry in the boost::openmethod namespace, which is
an alias to either debug or release, depending on the value of the
preprocessor symbol NDEBUG.
Usually not included directly. Can be used to create custom policies from stock policies, by forking them and adjusting a few policys.
<boost/openmethod/policies/basic_policy.hpp>
Provides the constructs used in the policy framework, essentially
basic_policy, policy, and its abstract subclasses (rtti, extern_vptr,
etc).
<boost/openmethod/policies/std_rtti.hpp>
Implements the rtti policy using standard RTTI.
<boost/openmethod/policies/minimal_rtti.hpp>
Implements the rtti policy using a minimal RTTI implementation. Can be used only with the "final" constructs, or with intrusive v-table pointers.
<boost/openmethod/policies/vptr_vector.hpp>
Implements the extern_vptr policy using a vector of pointers.
<boost/openmethod/policies/vptr_map.hpp>
Implements the extern_vptr policy using a map of pointers.
<boost/openmethod/policies/fast_perfect_hash.hpp>
Implements the type_hash policy using a perfect hash function.
<boost/openmethod/policies/default_error_handler.hpp>
Implements the error_handler policy by routing the error through a
std::function.
<boost/openmethod/policies/throw_error_handler.hpp>
Implements the error_handler policy by throwing an exception.
<boost/openmethod/policies/basic_error_output.hpp>
Implements the output policy using a lightweight version of
std::ostream.
<boost/openmethod/policies/basic_trace_output.hpp>
Implements the trace policy using a lightweight version of
std::ostream.
BOOST_OPENMETHOD
Synopsis
Defined in <boost/openmethod/macros.hpp>.
BOOST_OPENMETHOD(NAME, (PARAMETERS...), RETURN_TYPE [, POLICY]);
Description
Declares a method.
The macro expands to several constructs:
-
A
structforward declaration that acts as the method’s identifier:
struct BOOST_OPENMETHOD_ID(NAME);
-
An inline function template, constrained to take the same
PARAMETERS, without thevirtual_decorators, returning aRETURN_TYPE. The function forwards to
method<BOOST_OPENMETHOD_ID(NAME)(PARAMETERS…), RETURN_TYPE, POLICY>::fn. -
A guide function used to match overriders with the method:
auto BOOST_OPENMETHOD_ID(NAME)_guide(...)
-> ::boost::openmethod::method<
BOOST_OPENMETHOD_ID(NAME)(PARAMETERS...), RETURN_TYPE [, POLICY]>;
|
Note
|
NAME must be an identifier. Qualified names are not allowed.
|
|
Note
|
The default value for POLICY is the value of
BOOST_OPENMETHOD_DEFAULT_REGISTRY 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:
-
fnis the overrider function. -
has_next()returnstrueif 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.
|
Note
|
NAME must be an identifier. Qualified names are not allowed.
|
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.
|
Note
|
NAME must be an identifier. Qualified names are not allowed.
|
BOOST_OPENMETHOD_DECLARE_OVERRIDER
Synopsis
Defined in <boost/openmethod/macros.hpp>.
#define BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, (PARAMETERS...), RETURN_TYPE)
Description
Declares an overrider for 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:
-
fnis the overrider function. -
has_next()returnstrueif 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.
BOOST_OPENMETHOD_DECLARE_OVERRIDER can be called in a header file, with a
semicolon after the call. It can be called in a header file, but not multiple
times in the same translation unit.
|
Note
|
NAME must be an identifier. Qualified names are not allowed.
|
BOOST_OPENMETHOD_DEFINE_OVERRIDER
Synopsis
Defined in <boost/openmethod/macros.hpp>.
#define BOOST_OPENMETHOD_DEFINE_OVERRIDER(NAME, (PARAMETERS...), RETURN_TYPE)
Description
Defines the body of an overrider declared with
BOOST_OPENMETHOD_DECLARE_OVERRIDER. It should be called in an implementation
file, and followed by a function body.
|
Note
|
NAME must be an identifier. Qualified names are not allowed.
|
BOOST_OPENMETHOD_OVERRIDER
Synopsis
Defined in <boost/openmethod/macros.hpp>.
#define BOOST_OPENMETHOD_OVERRIDER(NAME, (PARAMETERS...), RETURN_TYPE)
Description
Expands to the specialization of the class template that contains the overrider for with the given name, parameter list and return type.
BOOST_OPENMETHOD_ID
Synopsis
Defined in <boost/openmethod/macros.hpp>.
#define BOOST_OPENMETHOD_ID(NAME) /* unspecified */
Description
Generates a long, obfuscated name from a short name. All the other names generated by macros are based on this name.
BOOST_OPENMETHOD_GUIDE
Synopsis
Defined in <boost/openmethod/macros.hpp>.
#define BOOST_OPENMETHOD_GUIDE(NAME) /* unspecified */
Description
Expands to the name of the guide function used to match overriders to methods.
BOOST_OPENMETHOD_OVERRIDERS
Synopsis
Defined in <boost/openmethod/macros.hpp>.
#define BOOST_OPENMETHOD_OVERRIDERS(NAME) \
BOOST_PP_CAT(BOOST_OPENMETHOD_ID(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.
BOOST_OPENMETHOD_REGISTER
Synopsis
Defined in <boost/openmethod/macros.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_REGISTRY 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_REGISTRY
Description
The name of the default policy.
BOOST_OPENMETHOD_DEFAULT_REGISTRY 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_registry is used.
BOOST_OPENMETHOD_DEFAULT_REGISTRY 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_REGISTRY. -
Include
<boost/openmethod/core.hpp>.
initialize
Synopsis
Defined in <boost/openmethod/compiler.hpp>.
namespace boost::openmethod {
template<class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY>
auto 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.
The return value is an object that contains a member variable, report, that
contains the following information:
-
std::size_t cells: the number of cells used by the v-tables and the multiple dispatch tables. -
std::size_t not_implemented: the number of methods that don’t have an overrider for at least one combination of virtual arguments. -
std::size_t ambiguous: the number of methods that have more than one overrider, none of which is more specific than the others, for at least one combination of virtual arguments.
finalize
Synopsis
Defined in <boost/openmethod/compiler.hpp>.
namespace boost::openmethod {
template<class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY>
auto finalize() -> void;
}
Description
De-allocates the resources allocated by initialize for the Policy, including
resources allocated by the policys in Policy. Resources are de-allocated in an
arbitrary order. It is not necessary to call finalize between calls to
initialize. It is provided mainly for the benefit of memory leak detection
schemes.
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_REGISTRY>
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_ptrs 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
resultis avirtual_ptr, get the pointer to the v-table from it. -
If a function named
boost_openmethod_vptrthat takesresultand returns avptr_typeexists, 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_ptrs 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_REGISTRY>
class virtual_ptr {
public:
static constexpr bool is_smart_ptr = /* see below */;
using element_type = /* see below */;
virtual_ptr();
virtual_ptr(nullptr_t);
template<class Other> virtual_ptr(Other& other);
template<class Other> virtual_ptr(const Other& other);
template<class Other> virtual_ptr(Other&& other);
virtual_ptr& operator =(nullptr_t);
template<class Other> virtual_ptr& operator =(Other& other);
template<class Other> virtual_ptr& operator =(const Other& other);
template<class Other> virtual_ptr& operator =(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_REGISTRY>;
template<class Class>
inline auto final_virtual_ptr(Class& obj) -> virtual_ptr<
Class, BOOST_OPENMETHOD_DEFAULT_REGISTRY>;
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/interop/std_shared_ptr.hpp>:
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY>
using shared_virtual_ptr = virtual_ptr<std::shared_ptr<Class>, Policy>;
template<
class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY, typename... T>
inline auto make_shared_virtual(T&&... args)
-> shared_virtual_ptr<Class, Policy>;
}
Defined in <boost/openmethod/interop/std_unique_ptr.hpp>:
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY>
using unique_virtual_ptr = virtual_ptr<std::unique_ptr<Class>, Policy>;
template<
class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY, 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 supported.
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
virtual_ptr(); // 1
virtual_ptr(nullptr_t); // 2
template<class Other> virtual_ptr(Other& other); // 3
template<class Other> virtual_ptr(const Other& other); // 4
template<class Other> virtual_ptr(Other&& other); // 5
template<class Other> virtual_ptr(Other* other); // 6
(1) Default constructor. Sets the v-table pointer to nullptr. If Class is
not a smart pointer, the value of object pointer is is undefined.
(2) Sets both the object and v-table pointers to nullptr.
(3), (4) For plain virtual_ptrs, other must be either a lvalue
reference to an object of a registered class, or a virtual_ptr (plain or
smart). For smart virtual_ptrs, other must be a reference to a smart
pointer, or a reference to a smart virtual_ptr.
(5) Constructs a virtual_ptr from a smart pointer or a smart virtual_ptr.
The object pointer is moved from other.
(6) Constructs a virtual_ptr from a plain pointer. Available only for plain
virtual_ptrs.
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_ptrs.
assignment operators
virtual_ptr& operator =(nullptr_t); // 1
template<class Other> virtual_ptr& operator =(Other& other); // 2
template<class Other> virtual_ptr& operator =(const Other& other); // 3
template<class Other> virtual_ptr& operator =(Other&& other); // 4
template<class Other> virtual_ptr& operator =(Other* other); // 5
(1) Sets both the object and v-table pointers to nullptr.
(2), (3) For plain virtual_ptrs, other must be either a lvalue
reference to an object of a registered class, or a virtual_ptr (plain or
smart). For smart virtual_ptrs, other must be a reference to a smart
pointer, or a reference to a smart virtual_ptr.
(4) Moves other to this virtual_ptr. If other is a smart pointer or a
smart virtual pointer, the object pointer is moved from other.
(5) Sets the object pointer to other. Available only for plain
virtual_ptrs.
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_ptrs.
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_REGISTRY>;
Non-members
virtual_shared_ptr
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY>
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_REGISTRY>
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_REGISTRY is used.
make_shared_virtual
template<
class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY, 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_REGISTRY, 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&& -
T* -
std::shared_ptr<T>: defined in <boost/openmethod/interop/std_shared_ptr.hpp> -
const std::shared_ptr<T>&: defined in <boost/openmethod/interop/std_shared_ptr.hpp> -
std::unique_ptr<T>: defined in <boost/openmethod/interop/std_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_REGISTRY>
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_REGISTRY 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&&, 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.
inplace_vptr
Synopsis
Defined in <boost/openmethod/inplace_vptr.hpp>.
namespace boost::openmethod {
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_REGISTRY>
class inplace_vptr {
protected:
inplace_vptr();
~inplace_vptr();
friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
};
template<class Class, class Base, class... MoreBases>
class inplace_vptr {
protected:
inplace_vptr();
~inplace_vptr();
friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
// if sizeof(MoreBases...) > 0
};
} // namespace boost::openmethod
Description
inplace_vptr is a CRTP class template that embeds and manages a vptr across a
class hierarchy.
If Class has no Bases, inplace_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, inplace_vptr may also declare one or two free
functions (boost_openmethod_policy and boost_openmethod_bases) at certain
levels of the hierarchy.
Members
constructor
inplace_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
~inplace_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
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 = /*unspecified*/;
template<class NewPolicy>
using fork = /*unspecified*/;
template<class... Facets>
using with = /*unspecified*/;
template<class... Facets>
using without = /*unspecified*/;
};
struct release : basic_policy<release, ...> {};
struct debug : release::add<...> {};
} // policies
#ifdef NDEBUG
using default_registry = policies::release;
#else
using default_registry = policies::debug;
#endif
} // boost::openmethod
Headers
Defined in <boost/openmethod/policies/basic_policy.hpp>. Also available via
<boost/openmethod/core.hpp> and <boost/openmethod.hpp>.
Description
basic_policy implements a policy, which consists of a a collection of methods,
classes, dispatch data, and policys, 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.
basic_policy has state. It uses the Curiously Recurring Template Pattern to
allow distinct policies to have distinct sets of static variables.
Members
has
template<class Facet>
static constexpr bool has;
Evaluates to true if Policy contains Facet.
fork
template<class NewPolicy>
using fork;
Creates a new policy from an existing one. NewPolicy does 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.
fork forks the policys in the policy as well: any policy instantiated from a
class template is assumed to take a policy as its first template argument. The
template is re-instantiated with the new policy as the first arguments, while
the other arguments remain the same.
with
template<class... Facets>
using with;
- Requires
-
Facets is a list of classes that derive from
policy. - Returns
-
A new policy containing Facets, and the policys from the original that do not have the same category as Facets.
- Examples
-
-
struct dyn_load : default_registry::fork<dyn_load>::with<indirect_vptr> {};
Creates a policy just likedefault_registry, with an extra indirection added to the v-table pointers. This policy is suitable for use with dynamic loading. -
struct release_with_diags : release::fork<release_with_diags>::with<basic_error_output<release_with_diags>> {};
Creates a policy just likerelease, except that it prints a diagnostic message before terminating withabort(). -
struct default_throw : default_registry::fork<default_throw>::with<throw_error_handler> {};
Creates a policy just likedefault_registry, except that it reports errors by throwing exceptions, instead of calling astd::functionlike the default error handler does.
-
without
template<class... Facets>
using without;
- Requires
-
Facets is a list of policy categories.
- Returns
-
A new policy containing the policys from the original that do not have the same category as Facets.
- Examples
-
-
struct use_map : default_registry::fork<use_map>::with<vptr_map<use_map>>::without<type_hash> {};
Creates a policy just likedefault_registry, except that it stores pointers to v-table in astd::unordered_map. Also removes the hash function, since it will not be used.
-
Non-members
release
struct release;
A policy that contains policys std_rtti, fast_perfect_hash, vptr_vector and
default_error_handler.
debug
struct debug;
The release policy with additional policy 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_registry
An alias for release if NDEBUG is defined, and for debug otherwise.
policy
Synopsis
Defined in <boost/openmethod/policies/basic_policy.hpp>.
namespace boost::openmethod::policies {
struct policy {
static auto finalize() -> void;
};
} // boost::openmethod::policies
Description
policy is the base class of all policys. 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 : policy {};
} // boost::openmethod::policies
Description
The rtti policy 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
is_polymorphic
template<class Class>
static constexpr bool is_polymorphic;
true if Class is polymorphic.
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 policy 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 policy 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 policy in the
process. This can result in collecting the type ids before they have been
initialized. Adding this policy to a policy moves the collection of type ids to
initialize.
minimal_rtti
Synopsis
struct minimal_rtti : rtti {
template<class Class>
static constexpr bool is_polymorphic = false;
template<typename Class>
static auto static_type() -> type_id;
};
Description
minimal_rtti is an implementation of the rtti policy that only uses static
type information.
minimal_rtti provides the only function strictly required for the rtti
policy.
This policy can be used in programs that call methods solely via
virtual_ptrs created with the "final" constructs. Virtual inheritance
is not supported. Classes are not required to be polymorphic.
Members
is_polymorphic
template<class Class>
static constexpr bool is_polymorphic = false;
This policy does not support polymorphic classes.
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 : policy {};
}
Description
extern_vptr is a policy 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 : policy {};
Description
indirect_vptr is a policy that makes virtual_ptrs and inplace_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 policy is its own implementation.
vptr_vector
Synopsis
Defined in <boost/openmethod/policies/vptr_vector.hpp>.
namespace boost::openmethod::policies {
template<class Policy>
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 Policy contains indirect_vptr, a level
of indirection is added, making the policy usable in presence of dynamic
loading.
Policy is the policy containing the policy.
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 policy, call its hash_initialize
function, and uses it to convert the type_ids 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 policy, 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 MapAdaptor = mp11::mp_quote<std::unordered_map>>
class vptr_map : public extern_vptr {
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. If `Policy contains indirect_vptr, a level of
indirection is added, making the policy usable in presence of dynamic loading.
Policy is the policy containing the policy.
MapAdaptor is a Boost.Mp11 quoted metafunction that returns a map type.
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 policy, checks if Class is
registered. If it is not, and Policy contains a error_handler policy, 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 : policy {};
} // boost::openmethod::policies
Description
type_hash is a policy that provides a hash function for a fixed set of
type_ids.
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_ids 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_ids.
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 policy, 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 policy, 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 policy, if present, the
calls abort.
If the policy has a trace policy, 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 {
namespace policies {
struct error_handler;
}
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;
};
}
Description
error_handler is a policy 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 policy 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;
default_error_handler
Synopsis
Defined in <boost/openmethod/policies/default_error_handler.hpp>.
namespace boost::openmethod::policies {
template<class Policy>
class default_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
default_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 output policy, 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 policy that
throws the error as an exception.
Members
error
template<class Error>
[[noreturn]] static auto error(const Error& error) -> void;
Throws error.
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 : output {
static Stream error_stream;
};
}
Description
basic_error_output is an implementation of output that writes error
messages to a LightweightOutputStream.
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.
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 {
static bool trace_enabled;
static Stream trace_stream;
};
}
Description
basic_error_output is an implementation of trace that writes error
messages to a LightweightOutputStream.
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.
LightweightOutputStream
Description
LightweightOutputStream 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 LightweightOutputStream provide the following functions:
| Name | Description |
|---|---|
LightweightOutputStream& operator<<(LightweightOutputStream& os, const char* str) |
Write a null-terminated string |
LightweightOutputStream& operator<<(LightweightOutputStream& os, const std::string_view& view) |
Write a view to `os |
LightweightOutputStream& operator<<(LightweightOutputStream& os, const void* value) |
Write a representation of a pointer to |
LightweightOutputStream& operator<<(LightweightOutputStream& os, std::size_t value) |
Write an unsigned integer to |