Introduction

Open-methods are similar to virtual functions, but they are not required to be members of a class. By being both free and virtual, they provide a solution to the Expression Problem:

Given a set of types, and a set of operations on these types, is it possible to add new operations on the existing types, and new types to the existing operations, without modifying existing code?

As a bonus, open-methods can take more than one argument into account when selecting the appropriate function to call - aka multiple dispatch. For that reason, open-methods are often called multi-methods, but that term is misleading, as it suggests that the feature is useful only when multiple dispatch is needed. In reality, it has been observed that, in large systems written in languages that support multi-methods, most methods use single-dispatch. The real benefit is in the solution to the Expression Problem.

Open-methods were introduced by the Common Lisp Object System, and they are native to many languages: Clojure, Julia, Dylan, TADS, Cecil, Diesel, Nice, etc. Bjarne Stroustrup wanted open-methods in C++ almost from the beginning. In D&E he writes:

I repeatedly considered a mechanism for a virtual function call based on more than one object, often called multi-methods. I rejected multi-methods with regret because I liked the idea, but couldn’t find an acceptable form under which to accept it. […​] Multi-methods is one of the interesting what-ifs of C++. Could I have designed and implemented them well enough at the time? Would their applications have been important enough to warrant the effort? What other work might have been left undone to provide the time to design and implement multi-methods? Since about 1985, I have always felt some twinge of regret for not providing multi-methods (Stroustrup, 1994, The Design and Evolution of C++, 13.8).

Circa 2007, he and his PhD students Peter Pirkelbauer and Yuriy Solodkyy wrote a series of papers and a prototype implementation based on the EDG compiler. Unfortunately, open-methods never made it into the standard. Stroustrup bemoans, in a more recent paper:

In retrospect, I don’t think that the object-oriented notation (e.g., x.f(y)) should ever have been introduced. The traditional mathematical notation f(x,y) is sufficient. As a side benefit, the mathematical notation would naturally have given us multi-methods, thereby saving us from the visitor pattern workaround (Stroustrup, 2020, Thriving in a Crowded and ChangingWorld: C++ 2006–2020).

This library implements the features described in the N2216 paper, with some extensions:

  • a mechanism for calling the next most specialized overrider

  • support for smart pointers

  • customization points for RTTI, error handling, tracing, smart pointers…​

Multiple and virtual inheritance are supported, with the exception of repeated inheritance.

Tutorials

Hello World

Consider the following program, intended to demonstrate the basics of virtual functions:

#include <iostream>
#include <memory>

struct Animal {
    Animal(std::string name) : name(name) {}
    virtual ~Animal() = default;
    virtual void poke(std::ostream&) = 0;
    std::string name;
};

struct Cat : Animal {
    using Animal::Animal;

    void poke(std::ostream& os) override {
        os << name << " hisses";
    }
};

struct Dog : Animal {
    using Animal::Animal;

    void poke(std::ostream& os) override {
        os << name << " barks";
    }
};

struct Bulldog : Dog {
    using Dog::Dog;

    void poke(std::ostream& os) override {
        Dog::poke(os);
        os << " and bites back";
    }
};

auto main() -> int {
    std::unique_ptr<Animal> a(new Cat("Felix"));
    std::unique_ptr<Animal> b(new Dog("Snoopy"));
    std::unique_ptr<Animal> c(new Bulldog("Hector"));

    a->poke(std::cout); // prints "Felix hisses"
    std::cout << ".\n";

    b->poke(std::cout); // prints "Snoopy barks"
    std::cout << ".\n";

    c->poke(std::cout); // prints "Hector barks and bites back"
    std::cout << ".\n";

    return 0;
}

We are going to rewrite this using open-methods.

First we remove the poke virtual functions from the domain classes:

#include <string>

struct Animal {
    Animal(std::string name) : name(name) {}
    std::string name;
    virtual ~Animal() = default;
};

struct Cat : Animal {
    using Animal::Animal;
};

struct Dog : Animal {
    using Animal::Animal;
};

struct Bulldog : Dog {
    using Dog::Dog;
};

Note that the Animal classes do not depend on iostreams anymore. This is a major advantage of open-methods over virtual functions: they make it possible to better organize dependencies.

Let’s implement poke. First we need to include the library’s main header. It defines a few macros, and injects a name - virtual_ptr - in the global namespace.

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

BOOST_OPENMETHOD(
    poke,                                       // method name
    (std::ostream&, virtual_ptr<Animal>),       // method signature
    void);                                      // return type

This defines a free function called poke, which takes two arguments. The first is the ostream. The second argument corresponds to the implicit this pointer in a virtual function. It is now an explicit argument. Just like with virtual functions, the exact function to execute is selected on the basis of the argument’s dynamic type.

Unlike virtual functions, there is no such thing as a pure open-method that would make a class abstract. It is not possible to determine if an overrider is available from looking at just the current translation unit.

Let’s add overriders for Cat and Dog:

BOOST_OPENMETHOD_OVERRIDE(
    poke,                                       // method name
    (std::ostream & os, virtual_ptr<Cat> cat),  // overrider signature
    void) {                                     // return type
    os << cat->name << " hisses";               // overrider body
}

BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, virtual_ptr<Dog> dog), void) {
    os << dog->name << " barks";
}

Bulldog::poke calls the poke it overrides in its Dog base. The equivalent for open-methods is next, a function that is available only inside the body of an overrider. It calls the next most specific overrider, i.e. what would have been called if the overrider did not exist.

BOOST_OPENMETHOD_OVERRIDE(
    poke, (std::ostream & os, virtual_ptr<Bulldog> dog), void) {
    next(os, dog);                              // call base overrider
    os << " and bites back";
}

All classes involved in open-method calls need to be registered using the BOOST_OPENMETHOD_CLASSES macro:

BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, Bulldog);

Classes can be registered incrementally, as long as all the direct bases of a class are listed with it in some call(s) to BOOST_OPENMETHOD_CLASSES. For example, Bulldog can be added in a second call, as long as Dog is listed as well:

// in animals.cpp
BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);

// in bulldog.cpp
BOOST_OPENMETHOD_CLASSES(Dog, Bulldog);

boost::openmethod::initialize(); must be called before any open-method call. It builds the dispatch tables. Typically this is done in main:

#include <boost/openmethod/compiler.hpp>
    // only needed in the file that calls boost::openmethod::initialize()

auto main() -> int {
    boost::openmethod::initialize();
    // ...
}

We call poke like any ordinary function. We can pass it the animals by reference, because virtual_ptr has a conversion constructor for that:

    std::unique_ptr<Animal> felix(new Cat("Felix"));
    std::unique_ptr<Animal> snoopy(new Dog("Snoopy"));
    std::unique_ptr<Animal> hector(new Bulldog("Hector"));

    poke(std::cout, *felix); // Felix hisses
    std::cout << ".\n";

    poke(std::cout, *snoopy); // Snoopy barks
    std::cout << ".\n";

    poke(std::cout, *hector); // Hector barks and bites
    std::cout << ".\n";
Note
virtual_ptr is more like a reference than a pointer: it cannot be null, and it cannot be re-assigned. The only reason why it is not called virtual_ref is to save the name in case it becomes possible to overload the dot operator in future versions of C++.

Multiple Dispatch

A method can have more than one virtual_ptr parameter. For example:

BOOST_OPENMETHOD(
    encounter,
    (std::ostream&, virtual_ptr<Animal>, virtual_ptr<Animal>), void);

// 'encounter' catch-all implementation.
BOOST_OPENMETHOD_OVERRIDE(
    encounter,
    (std::ostream & os, virtual_ptr<Animal> a, virtual_ptr<Animal> b), void) {
    os << a->name << " and " << b->name << " ignore each other";
}

// Add definitions for specific pairs of animals.
BOOST_OPENMETHOD_OVERRIDE(
    encounter,
    (std::ostream & os, virtual_ptr<Dog> dog1, virtual_ptr<Dog> dog2), void) {
    os << "Both wag tails";
}

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (std::ostream & os, virtual_ptr<Dog> dog, virtual_ptr<Cat> cat),
    void) {
    os << dog->name << " chases " << cat->name;
}

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (std::ostream & os, virtual_ptr<Cat> cat, virtual_ptr<Dog> dog),
    void) {
    os << cat->name << " runs away from " << dog->name;
}
// cat and dog
encounter(std::cout, *felix, *snoopy); // Felix runs away from Snoopy
std::cout << ".\n";

// dog and cat
encounter(std::cout, *snoopy, *felix); // Snoopy chases Felix
std::cout << ".\n";

// dog and dog
encounter(std::cout, *snoopy, *hector); // Both wag tails
std::cout << ".\n";

// cat and cat
std::unique_ptr<Animal> tom(new Cat("Tom"));
encounter(std::cout, *felix, *tom); // Felix and Tom ignore each other
std::cout << ".\n";

The appropriate overrider is selected using a process similar to overload resolution, with fallback options. If one overrider is more specialized than all the others, call it. Otherwise, the return type is used as a tie-breaker, if it is covariant with the return type of the base method. If there is still no unique best overrider, one of the best overriders is chosen arbitrarily.

Friendship

Overriders are implemented as static functions located in specializations of a template named after the method, declared in the same scope. Macro BOOST_OPENMETHOD_OVERRIDERS returns that name. The template argument for a specialization is the signature of the overrider. For example, the overrider of poke for Cat is:

BOOST_OPENMETHOD_OVERRIDERS(poke)<
    void(std::ostream& os, virtual_ptr<Cat> cat)>::fn;

We can thus grant friendship to all the overriders of poke:

class Cat;
class Dog;

class Animal {
    // ...
  private:
    std::string name;

    template<typename> friend struct BOOST_OPENMETHOD_OVERRIDERS(poke);
};

Be aware, though, that the overriders of any method called poke - even with a different signature - are granted friendship.

We can also grant friendship to individual overriders:

class Cat;
class Dog;

template<typename> struct BOOST_OPENMETHOD_OVERRIDERS(poke);

class Animal {
    // ...
  private:
    std::string name;

    friend struct BOOST_OPENMETHOD_OVERRIDERS(poke)<void(std::ostream&, virtual_ptr<Cat>)>;
    friend struct BOOST_OPENMETHOD_OVERRIDERS(poke)<void(std::ostream&, virtual_ptr<Dog>)>;
};

Performance

Open-methods are almost as fast as ordinary virtual member functions when compiled with optimization.

clang compiles the following code:

void call_poke_via_ref(std::ostream& os, Animal& a) {
    poke(os, a);
}

…​to this on the x64 architecture (variable names have been shortened for readability):

mov	    rax, qword ptr [rsi]
mov	    rdx, qword ptr [rip + hash_mult]
imul	rdx, qword ptr [rax - 8]
movzx	ecx, byte ptr [rip + hash_shift]
shr	    rdx, cl
mov	    rax, qword ptr [rip + vptrs]
mov	    rax, qword ptr [rax + 8*rdx]
mov	    rcx, qword ptr [rip + poke::slots_strides]
mov	    rax, qword ptr [rax + 8*rcx]
jmp	    rax

llvm-mca estimates a throughput of 4 cycles per dispatch. Comparatively, calling a native virtual functions takes one cycle. However, the difference is amortized by the time spent passing the arguments and returning from the function; plus, of course, executing the body of the function.

Micro benchmarks suggest that dispatching an open-methods with a single virtual argument is between 30% and 50% slower than calling the equivalent virtual function, with an empty body and no other arguments.

However, call_poke does two things: it constructs a virtual_ptr<Animal> from an Animal&; and then it calls the method. The construction of the virtual_ptr is the costly part, as it involves a hash table lookup. Once that price has been paid, the virtual_ptr can be used multiple times. It is passed to the overrider, which can make further method calls through it. It can be stored in variables in place of plain pointers.

Let’s look at another example: an AST for an arithmetic calculator:

#include <iostream>

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

struct Node {
    virtual ~Node() {}
};

struct Literal : Node {
    explicit Literal(int value) : value(value) {}

    int value;
};

struct Plus : Node {
    Plus(virtual_ptr<Node> left, virtual_ptr<Node> right)
        : left(left), right(right) {}

    virtual_ptr<Node> left, right;
};

struct Negate : Node {
    explicit Negate(virtual_ptr<Node> node) : child(node) {}

    virtual_ptr<Node> child;
};

BOOST_OPENMETHOD(value, (virtual_ptr<Node>), int);

BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Literal> node), int) {
    return node->value;
}

BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Plus> node), int) {
    return value(node->left) + value(node->right);
}

BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Negate> node), int) {
    return -value(node->child);
}

BOOST_OPENMETHOD_CLASSES(Node, Literal, Plus, Negate);

auto main() -> int {
    boost::openmethod::initialize();

    Literal one(1), two(2);
    Plus sum(one, two);
    Negate neg(sum);

    std::cout << value(neg) << "\n"; // -3

    return 0;
}

The Negate overrider compiles to:

mov	rdi,    qword ptr [rsi + 8]
mov	rsi,    qword ptr [rsi + 16]

mov	rax,    qword ptr [rip + value::slots_strides]
call	    qword ptr [rdi + 8*rax]

neg	        eax
pop	        rcx

The first two instructions read the virtual_ptr from this - placing its content in registers rdi and rsi.

The next two instructions are the method call proper. According to llvm-mca, they take one cycle - the same as a native virtual function call.

When we create the Plus and Negate nodes, we call the conversion constructors of virtual_ptr<Node>, which occur the cost of hash table lookups. However, in this example, we know the exact types of the objects. In that case, we can use final_virtual_ptr to construct the virtual_ptr using a single instruction. For example:

Literal one(1);
Negate neg(boost::openmethod::final_virtual_ptr(one));

…​compiles to:

;; construct Literal
lea	rax, [rip + vtable for Literal+16]
mov	qword ptr [rsp], rax
mov	dword ptr [rsp+8], 1

;; construct Negate
mov	rax, qword ptr [rip+static_vptr<Literal>] ; address of openmethod v-table
lea	rcx, [rip+vtable for Negate+16]           ; address of native v-table
mov	qword ptr [rsp+16], rcx                   ; set native v-table
mov	qword ptr [rsp+24], rax                   ; set openmethod v-table
mov	rax, rsp                                  ; address of 'one'
mov	qword ptr [rsp+32], rax                   ; set vptr object pointer to 'one'

final_virtual_ptr does not require its argument to have a polymorphic type.

Smart Pointers

virtual_ptr can also be used in combination with smart pointers. virtual_ptr<std::shared_ptr<Class>> (aliased to shared_virtual_ptr<Class>) and virtual_ptr<std::unique_ptr<Class>> (aliased to unique_virtual_ptr<Class>) deliver the convenience of automatic memory management with the speed of virtual_ptr. Convenience functions make_shared_virtual and make_unique_virtual create an object and return a smart virtual_ptr to it. Since the exact type of the object is known, the vptr is read from a static variable, without incuring the cost of a hash table lookup.

Here is a variaton of the AST example that uses dynamic allocation and unique pointers:

#include <iostream>
#include <memory>

#include <boost/openmethod.hpp>
#include <boost/openmethod/unique_ptr.hpp>
#include <boost/openmethod/compiler.hpp>

using boost::openmethod::unique_virtual_ptr;
using boost::openmethod::make_unique_virtual;

struct Node {
    virtual ~Node() {}
};

struct Literal : Node {
    Literal(int value) : value(value) {}

    int value;
};

struct Plus : Node {
    Plus(unique_virtual_ptr<Node> left, unique_virtual_ptr<Node> right)
        : left(std::move(left)), right(std::move(right)) {}

    unique_virtual_ptr<Node> left, right;
};

struct Negate : Node {
    Negate(unique_virtual_ptr<Node> node) : child(std::move(node)) {}

    unique_virtual_ptr<Node> child;
};

BOOST_OPENMETHOD(value, (virtual_ptr<Node>), int);

BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Literal> node), int) {
    return node->value;
}

BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Plus> node), int) {
    return value(node->left) + value(node->right);
}

BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr<Negate> node), int) {
    return -value(node->child);
}

BOOST_OPENMETHOD_CLASSES(Node, Literal, Plus, Negate);

auto main() -> int {
    boost::openmethod::initialize();

    auto expr = make_unique_virtual<Negate>(
        make_unique_virtual<Plus>(
            make_unique_virtual<Literal>(1),
            make_unique_virtual<Literal>(2)));

    std::cout << value(expr) << "\n"; // -3

    return 0;
}

Alternatives to virtual_ptr

Virtual arguments can be passed as plain references. In a method declaration, parameters with a type decorated with virtual_ are considered in overrider selection (along with virtual_ptr parameters).

For example, the poke open-method in the Animals example can be rewritten as:

struct Animal {
    virtual ~Animal() = default;
};

struct Cat : Animal {};

using boost::openmethod::virtual_;

BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);

BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) {
    os << "hiss";
}

BOOST_OPENMETHOD_CLASSES(Animal, Cat);

int main() {
    boost::openmethod::initialize();

    Cat cat;
    poke(std::cout, cat); // hiss
}

Note that virtual_ is not used in the overrider. It is also removed from the method’s signature.

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

However, we can now customize how the vptr is obtained. When the method sees a virtual_ parameter, it looks for a boost_openmethod_vptr function that takes the parameter (by const reference), and returns a vptr_type. If one is found, it is called to obtain the vptr. The vptr for a specific registered class can be obtained via a variable template static_vptr, nested in class default_policy (more on policies below).

In the following example, we embed a vptr in the object, just like the vptr for native virtual functions:

class Animal {
  protected:
    boost::openmethod::vptr_type vptr;
    friend auto boost_openmethod_vptr(const Animal& a) {
        return a.vptr;
    }

  public:
    Animal() {
        vptr = boost::openmethod::default_policy::static_vptr<Animal>;
    }
};

class Cat : public Animal {
  public:
    Cat() {
        vptr = boost::openmethod::default_policy::static_vptr<Cat>;
    }
};

BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);

BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) {
    os << "hiss\n";
}

BOOST_OPENMETHOD_CLASSES(Animal, Cat);

int main() {
    boost::openmethod::initialize();

    Cat cat;
    poke(std::cout, cat); // hiss
}
Note
With this approach, classes need not be polymorphic. A virtual destructor might be needed for correct destruction of objects, but it is not required by the library.

The with_vptr CRTP class automates the creation and management of embedded vptrs.

#include <boost/openmethod/with_vptr.hpp>

class Animal : public boost::openmethod::with_vptr<Animal> {
};

class Cat : public Animal, public boost::openmethod::with_vptr<Cat, Animal> {
};

BOOST_OPENMETHOD(poke, (std::ostream&, virtual_<Animal&>), void);

BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) {
    os << "hiss\n";
}

int main() {
    boost::openmethod::initialize();

    Cat cat;
    poke(std::cout, cat); // hiss
}

If with_vptr is passed only the class being defined, it adds a vptr to it, and defines a boost_openmethod_vptr friend function. If more classes are passed, they must be the direct bases of the class potentially involved in open-method calls. Its constructor and destructor set the vptr to point to the v-table for the class. with_vptr also takes care of registering the classes, so this time the call to BOOST_OPENMETHOD_CLASSES is not needed.

Core API

OpenMethod provides a macro-free interface: the core API. This is useful in certain situations, for example when combining open-methods and templates.

Let’s rewrite the Animals example using the core API. An open-method is implemented as an instance of the method template. Its parameters are a function signature and a return type:

#include <boost/openmethod/core.hpp>

using namespace boost::openmethod;

class poke_openmethod;

using poke = method<
    poke_openmethod(std::ostream&, virtual_<Animal&>), void>;

The poke_openmethod class acts as the method’s identifier: it separates it from other methods with the same signature. The exact name does not really matter, and the class needs not be defined, only declared. Inventing a class name can get tedious, so OpenMethod provides a macro for that:

#include <boost/openmethod/macros/name.hpp>

class BOOST_OPENMETHOD_NAME(poke);

using poke = method<
    BOOST_OPENMETHOD_NAME(poke)(std::ostream&, virtual_ptr<Animal>), void>;
Note
BOOST_OPENMETHOD and associated macros use BOOST_OPENMETHOD_NAME in their implementation. This makes it possible to mix the "macro" and "core" styles.

We call the method via the nested function object fn:

poke::fn(std::cout, animal);

Overriders are ordinary functions, added to a method using the nested template override:

auto poke_cat(std::ostream& os, virtual_ptr<Cat> cat) {
    os << "hiss";
}

static poke::override<poke_cat> override_poke_cat;
Note
override can register multiple overriders.

In C++26, we will be able to use _ instead of inventing a one-time-use identifier. In the meantime, OpenMethod provides a small convenience macro:

#include <boost/openmethod/macros/register.hpp>

auto poke_dog(std::ostream& os, virtual_ptr<Dog> dog) {
    os << "bark";
}

BOOST_OPENMETHOD_REGISTER(poke::override<poke_dog>);

next is available from the method’s nested next template:

auto poke_bulldog(std::ostream& os, virtual_ptr<Bulldog> dog) -> void {
    poke::next<poke_bulldog>(os, dog);
    os << " and bite";
}

BOOST_OPENMETHOD_REGISTER(poke::override<poke_bulldog>);
Note
Since the function uses itself as a template argument in its body, its return type cannot be deduced. It must be specified explicitly, either by using the old function declaration style or a trailing return type.

Why not call poke_dog directly? We could; however, keep in mind that, in a real program, a translation unit is not necessarily aware of the overriders added elsewhere - especially in presence of dynamic loading.

We register the classes with use_classes:

BOOST_OPENMETHOD_REGISTER(use_classes<Animal, Cat, Dog, Bulldog>);

Finally, we call the method via the static member of the method class fn:

auto main() -> int {
    boost::openmethod::initialize();

    std::unique_ptr<Animal> a(new Cat);
    std::unique_ptr<Animal> b(new Dog);
    std::unique_ptr<Animal> c(new Bulldog);

    poke::fn(std::cout, *a); // prints "hiss"
    std::cout << "\n";

    poke::fn(std::cout, *b); // prints "bark"
    std::cout << "\n";

    poke::fn(std::cout, *c); // prints "bark and bite"
    std::cout << "\n";

    return 0;

Policies and Facets

Methods and classes are scoped in a policy. A method can only reference classes registered in the same policy. If a class is used as a virtual parameter in methods using different policies, it must be registered with each of them.

Class templates use_classes, method, virtual_ptr, and macros BOOST_OPENMETHOD and BOOST_OPENMETHOD_CLASSES, accept an additional argument, a policy class, which defaults to policies::debug in debug builds, and policies::release in release builds.

A policy has a collection of facets. Facets control how type information is obtained, how vptrs are fetched, how errors are handled and printed, etc. Some are used in initialize and method dispatch; some are used by other facets in the same policy as part of their implementation. See the reference for a list of facets and stock implementations. Policies and facets are placed in the boost::openmethod::policies namespace. Two policies are provided by the library: release and debug.

release contains the following facets:

facet implementations role

rtti

std_rtti, minimal_rtti

provides type information for classes and objects

extern_vptr

vptr_vector, vptr_map

stores vptrs in an indexed collection

type_hash

fast_perfect_hash

hash type id to an index in a vector

error_handler

vectored_error_handler, throw_error_handler

handles errors

policies::debug contains the same facets as release, plus a few more:

facet implementation role

runtime_checks

(itself)

enables runtime checks

error_output

basic_error_output

prints error descriptions to stderr

trace_output

basic_trace_output

enables initialize to print information about dispatch table construction to stderr

Policies, and some facets, have static variables. When it is the case, they are implemented as CRTP classes.

Policies can be created from scratch, using the basic_policy template, or by adding or removing facets from existing policies. For example, policies::debug is a tweak of policies::release:

namespace boost::openmethod::policies {

struct debug : release::add<
    runtime_checks, basic_error_output<debug>, basic_trace_output<debug>> {};
}

boost::openmethod::default_policy is an alias to release or debug, depending on the value of preprocessor symbols NDEBUG. The default policy can be overriden by defining the macroprocessor symbol BOOST_OPENMETHOD_DEFAULT_POLICY before including <boost/openmethod/core.hpp>. The value of the symbol is used as a default template parameter for use_classes, method, virtual_ptr, and others. Once the core header has been included, changing BOOST_OPENMETHOD_DEFAULT_POLICY has no effect. See below for examples.

Error Handling

When an error is encountered, the program is terminated by a call to abort. If the policy contains an error_handler facet, it provides an error member function (or overloaded functions) to be called with an object identifying the error. The release and debug policies implement the error facet with vectored_error_handler, which wraps the error object in a variant, and calls a handler via a std::function. By default, it prints a description of the error to stderr in the debug policy, and does nothing in the release policy. The handler can be set with set_error_handler:

#include <iostream>
#include <variant>

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

struct Animal {
    virtual ~Animal() = default;
};

struct Cat : Animal {};
struct Dog : Animal {};

BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);

BOOST_OPENMETHOD(trick, (std::ostream&, virtual_ptr<Animal>), void);

BOOST_OPENMETHOD_OVERRIDE(
    trick, (std::ostream & os, virtual_ptr<Dog> dog), void) {
    os << "spin\n";
}

auto main() -> int {
    namespace bom = boost::openmethod;
    bom::initialize();

    bom::default_policy::set_error_handler(
        [](const bom::default_policy::error_variant& error) {
            if (std::holds_alternative<bom::not_implemented_error>(error)) {
                throw std::runtime_error("not implemented");
            }
        });

    Cat felix;
    Dog hector, snoopy;
    std::vector<Animal*> animals = {&hector, &felix, &snoopy};

    for (auto animal : animals) {
        try {
            trick(std::cout, *animal);
        } catch (std::runtime_error& error) {
            std::cerr << error.what() << "\n";
        }
    }

    return 0;
}

Output:

spin
not implemented
spin

We can also replace the error_handler facet with our own. For example:

#include <iostream>

#include <boost/openmethod/policies.hpp>

struct Animal {
    virtual ~Animal() = default;
};

struct Cat : Animal {};
struct Dog : Animal {};

namespace bom = boost::openmethod;

struct throw_if_not_implemented : bom::policies::error_handler {
    static auto error(const bom::openmethod_error&) -> void {
    }

    static auto error(const bom::not_implemented_error& err) -> void {
        throw err;
    }
};

struct throwing_policy
    : bom::default_policy::fork<throwing_policy>::replace<
          bom::policies::error_handler, throw_if_not_implemented> {};

#define BOOST_OPENMETHOD_DEFAULT_POLICY throwing_policy

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

BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);

BOOST_OPENMETHOD(trick, (std::ostream&, virtual_ptr<Animal>), void);

BOOST_OPENMETHOD_OVERRIDE(
    trick, (std::ostream & os, virtual_ptr<Dog> dog), void) {
    os << "spin\n";
}

auto main() -> int {
    bom::initialize();

    Cat felix;
    Dog hector, snoopy;
    std::vector<Animal*> animals = {&hector, &felix, &snoopy};

    for (auto animal : animals) {
        try {
            trick(std::cout, *animal);
        } catch (bom::not_implemented_error) {
            std::cout << "not implemented\n";
        }
    }

    return 0;
}
spin
not implemented
spin

Stock facet throw_error_handler does this for all the exception types:

namespace boost::openmethod::policies {

struct throw_error_handler : error_handler {
    template<class Error>
    [[noreturn]] static auto error(const Error& error) -> void {
        throw error;
    }
};

} // namespace boost::openmethod::policies

Custom RTTI

Stock policies use the std_rtti implementation of rtti. Here is its full source:

struct std_rtti : rtti {
    template<typename T>
    static type_id static_type() {
        return reinterpret_cast<type_id>(&typeid(T));
    }

    template<typename T>
    static type_id dynamic_type(const T& obj) {
        return reinterpret_cast<type_id>(&typeid(obj));
    }

    template<class Stream>
    static void type_name(type_id type, Stream& stream) {
        stream << reinterpret_cast<const std::type_info*>(type)->name();
    }

    static std::type_index type_index(type_id type) {
        return std::type_index(*reinterpret_cast<const std::type_info*>(type));
    }

    template<typename D, typename B>
    static D dynamic_cast_ref(B&& obj) {
        return dynamic_cast<D>(obj);
    }
};
  • static_type is used by class registration, by virtual_ptr's "final" constructs, and to format error and trace messages. T is not restricted to the classes that appear as virtual parameters. This function is required.

  • dynamic_type is used to locate the v-table for an object. This function is usually required. If only the virtual_ptr "final" constructs are used, or if boost_openmethod_vptr is provided for all the classes in the policy, it can be omitted.

  • type_name writes a representation of type to stream. It is used to format error and trace messages. Stream is a lighweight version of std::ostream with reduced functionality. It only supports insertion of const char*, std::string_view, pointers and std::size_t. This function is optional; if it is not provided, "type_id(type)" is used.

  • type_index returns an object that uniquely identifies a class. Some forms of RTTI (most notably, C++'s typeid operator) do not guarantee that the type information object for a class is unique within the same program. This function is optional; if not provided, type is assumed to be unique, and used as is.

  • dynamic_cast_ref casts obj to class D. B&& is either a lvalue reference (possibly cv-qualified) or a rvalue reference. D has the same reference category (and cv-qualifier if applicable) as B. This function is required only in presence of virtual inheritance.

Consider a custom RTTI implementation:

struct Animal {
    Animal(unsigned type) : type(type) {
    }

    virtual ~Animal() = default;

    unsigned type;
    static constexpr unsigned static_type = 1;
};

struct Cat : Animal {
    Cat() : Animal(static_type) {
    }

    static constexpr unsigned static_type = 2;
};

// ditto for Dog

This scheme has an interesting property: its type ids are monotonically allocated in a small, dense range. Thus, we don’t need to hash them. We can use them as indexes in the table of vptrs.

This time we are going to replace the default policy globally. First we need to define the custom RTTI facet. We must not include <boost/openmethod/core.hpp> or any header that includes it yet.

Here is the facet implementation:

namespace bom = boost::openmethod;

struct custom_rtti : bom::policies::rtti {
    template<typename T>
    static auto static_type() -> bom::type_id {
        if constexpr (std::is_base_of_v<Animal, T>) {
            return T::static_type;
        } else {
            return 0;
        }
    }

    template<typename T>
    static auto dynamic_type(const T& obj) -> bom::type_id {
        if constexpr (std::is_base_of_v<Animal, T>) {
            return obj.type;
        } else {
            return 0;
        }
    }
};

This facet is quite minimal. It does not support virtual inheritance. It would not produce good error or trace messages, because types would be represented by their integer ids.

This time we create a policy from scratch. For that we use the basic_policy CRTP template:

struct custom_policy : bom::policies::basic_policy<
                           custom_policy, custom_rtti,
                           bom::policies::vptr_vector<custom_policy>> {};

#define BOOST_OPENMETHOD_DEFAULT_POLICY custom_policy

Now we can include the "core" header and write the example:

#include <iostream>

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

BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr<Animal>), void);

BOOST_OPENMETHOD_OVERRIDE(
    poke, (std::ostream & os, virtual_ptr<Cat> cat), void) {
    os << "hiss";
}

BOOST_OPENMETHOD_OVERRIDE(
    poke, (std::ostream & os, virtual_ptr<Dog> dog), void) {
    os << "bark";
}

BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog);

auto main() -> int {
    boost::openmethod::initialize();

    std::unique_ptr<Animal> a(new Cat);
    std::unique_ptr<Animal> b(new Dog);

    poke(std::cout, *a); // prints "hiss"
    std::cout << "\n";

    poke(std::cout, *b); // prints "bark"
    std::cout << "\n";

    return 0;
}

This programs compiles even if standard RTTI is disabled.

Deferred RTTI

In the previous example, the RTTI system assigns types id statically. It is more common to allocate them using a global counter, manipulated by static constructors. This is a problem, because static_type is used by class registration. It may read the custom type ids before they are have been initialized.

The solution is to add the deferred_static_rtti facet to the policy; it defers reading the type information until initialize is called.

This time let’s support virtual inheritance as well. First the domain classes:

struct custom_type_info {
    static unsigned last;
    unsigned id = ++last;
};

unsigned custom_type_info::last;

struct Animal {
    Animal() {
        type = type_info.id;
    }

    virtual ~Animal() = default;

    virtual auto cast_impl(unsigned target) -> void* {
        if (type_info.id == target) {
            return this;
        } else {
            return nullptr;
        }
    }

    template<class Class>
    auto cast() -> Class* {
        return reinterpret_cast<Class*>(cast_impl(Class::type_info.id));
    }

    static custom_type_info type_info;
    unsigned type;
};

custom_type_info Animal::type_info;

struct Cat : virtual Animal {
    Cat() {
        type = type_info.id;
    }

    virtual auto cast_impl(unsigned target) -> void* {
        if (type_info.id == target) {
            return this;
        } else {
            return Animal::cast_impl(target);
        }
    }

    static custom_type_info type_info;
};

custom_type_info Cat::type_info;
// ditto for Dog

The rtti facet is the same, with one more function:

struct custom_rtti : bom::policies::rtti {
    // as before

    // to support virtual inheritance:
    template<typename Derived, typename Base>
    static auto dynamic_cast_ref(Base&& obj) -> Derived {
        using base_type = std::remove_reference_t<Base>;
        if constexpr (std::is_base_of_v<Animal, base_type>) {
            return *obj.template cast<std::remove_reference_t<Derived>>();
        } else {
            abort(); // not supported
        }
    }
};

Finally, the policy contains an additional facet - deferred_static_rtti:

struct custom_policy
    : bom::policies::basic_policy<
          custom_policy, custom_rtti,
          bom::policies::deferred_static_rtti, // <-- additional facet
          bom::policies::vptr_vector<custom_policy>> {};

The example is the same as in the previous section.

Dynamic Loading

OpenMethod supports dynamic loading on operating systems that are capable of handling C++ templates correctly during dynamic link. A dynamic library can add classes, methods and overriders to an existing policy. initialize must then be called to rebuild the dispatch tables.

This leads to a problem: any virtual_ptr in existence before initialize is called again becomes invalid. This also applies to vptrs that are stored inside objects by with_vptr.

Note
This applies only to cases where a dynamic library adds to an existing policy. Even if the dynamic library itself uses open-methods, for example as an implementation detail, but it uses its own policy, there is no issue.

The solution is to use a policy that contains the indirect_vptr facet. Instead of storing the vptr directly, it stores a reference to the vptr.

Here is an example:

// dl.hpp

#include <string>

#include <boost/openmethod.hpp>

struct Animal {
    virtual ~Animal() {
    }
};

struct Herbivore : Animal {};
struct Carnivore : Animal {};
struct Cow : Herbivore {};
struct Wolf : Carnivore {};

struct dynamic_policy
    : boost::openmethod::default_policy::fork<dynamic_policy>::replace<
          boost::openmethod::policies::extern_vptr,
          boost::openmethod::policies::vptr_vector<
              dynamic_policy, boost::openmethod::policies::indirect_vptr>> {};
template<class Class>
using dyn_vptr = boost::openmethod::virtual_ptr<Class, dynamic_policy>;

BOOST_OPENMETHOD(
    encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string,
    dynamic_policy);
Note
The policy must be passed to the method as well as the virtual_ptrs.

The indirect_vptr facet tells virtual_ptr to use a reference to the vptr, instead of its value. Even tough the value of the vptr changes when initialize is called, the vptrs are stored in the same place (the policy’s static_vptr<Class> variables).

We can now register the classes and and provide an overrider:

// dl_main.cpp

#include <cstring>
#include <iostream>
#include <dlfcn.h>
#include <unistd.h>

#include <boost/openmethod.hpp>
#include <boost/openmethod/unique_ptr.hpp>
#include <boost/openmethod/compiler.hpp>

#include "dl.hpp"

BOOST_OPENMETHOD_CLASSES(
    Animal, Herbivore, Cow, Wolf, Carnivore, dynamic_policy);

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string) {
    return "ignore\n";
}

It is not necessary to pass the policy to BOOST_OPENMETHOD_CLASSES, because indirect_vptr does not have any state. As for BOOST_OPENMETHOD_OVERRIDE, it deduces the policy from the method.

At this point we only have one overrider. Animals of all species ignore one another:

auto main() -> int {
    using namespace boost::openmethod;

    initialize<dynamic_policy>();

    std::cout << "Before loading library\n";

    auto gracie = make_unique_virtual<Cow, dynamic_policy>();
    // Wolf _willy;
    // auto willy = virtual_ptr<Wolf, dynamic_policy>(_willy);
    auto willy = make_unique_virtual<Wolf, dynamic_policy>();

    std::cout << "Gracie encounters Willy -> "
              << encounter(gracie, willy); // ignore
    std::cout << "Willy encounters Gracie -> "
              << encounter(willy, gracie); // ignore

Let’s load a dynamic library containing this code:

// dl_shared.cpp

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

#include "dl.hpp"

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (dyn_vptr<Herbivore>, dyn_vptr<Carnivore>), std::string) {
    return "run\n";
}

struct Tiger : Carnivore {};

BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore, dynamic_policy);

extern "C" auto make_tiger() -> Tiger* {
    return new Tiger;
}

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (dyn_vptr<Carnivore>, dyn_vptr<Herbivore>), std::string) {
    return "hunt\n";
}

Now back to main:

    char dl_path[4096];
    dl_path[readlink("/proc/self/exe", dl_path, sizeof(dl_path))] = 0;
    *strrchr(dl_path, '/') = 0;
    strcat(dl_path, "/libdl_shared.so");
    void* handle = dlopen(dl_path, RTLD_NOW);

    if (!handle) {
        std::cerr << "dlopen() failed: " << dlerror() << "\n";
        exit(1);
    }

    std::cout << "\nAfter loading library\n";

    boost::openmethod::initialize<dynamic_policy>();

    auto make_tiger =
        reinterpret_cast<Animal* (*)()>(dlsym(handle, "make_tiger"));

    if (!make_tiger) {
        std::cerr << "dlsym() failed: " << dlerror() << "\n";
        exit(1);
    }

    std::cout << "Willy encounters Gracie -> "
              << encounter(willy, gracie); // hunt

    {
        auto hobbes = std::unique_ptr<Animal>(make_tiger());
        std::cout << "Gracie encounters Hobbes -> "
                  << encounter(gracie, *hobbes); // run
    }

After unloading the library, we must call initialize again:

    dlclose(handle);

    std::cout << "\nAfter unloading library\n";

    boost::openmethod::initialize<dynamic_policy>();

    std::cout << "Gracie encounters Willy -> "
              << encounter(gracie, willy); // ignore
    std::cout << "Willy encounters Gracie -> "
              << encounter(willy, gracie); // ignore

Reference

Overview

Requirements

OpenMethod requires C++17 or above. It depends on the following Boost libraries:

  • Assert

  • Config

  • Core

  • DynamicBitset

  • Mp11

  • Preprocessor

Boost.Test is also required to build and run the unit tests.

Installation

The library is headers-only. You can install it system-wide, or add the path to the include directory to your project’s include path.

Namespaces

boost::openmethod

The library’s main namespace. Contains method, virtual_ptr and virtual_ptr_traits, use_classes, the debug and release policies, etc.

boost::openmethod::policies

Contains the policy classes and their facets.

Headers

<boost/openmethod/core.hpp>

The library’s main header. Contains method, virtual_ptr and virtual_ptr_traits, use_classes, the debug and release policies, etc.

<boost/openmethod/compiler.hpp>

Defines intialize and finalize, which are used to register classes and

<boost/openmethod/macros.hpp>
<boost/openmethod.hpp>
<boost/openmethod/policies.hpp>
<boost/openmethod/shared_ptr.hpp>
<boost/openmethod/unique_.hpp>
<boost/openmethod/with_vptr.hpp>

BOOST_OPENMETHOD

Synopsis

Defined in <boost/openmethod/macros.hpp>.

BOOST_OPENMETHOD(NAME, (PARAMETERS...), RETURN_TYPE [, POLICY]);

Description

Declares a method.

Effects

The macro expands to several constructs:

  • A struct forward declaration that acts as the method’s identifier:

struct BOOST_OPENMETHOD_NAME(NAME);
  • An inline function template, constrained to take the same PARAMETERS, without the virtual_ decorators, returning a RETURN_TYPE. The function forwards to
    method<BOOST_OPENMETHOD_NAME(NAME)(PARAMETERS…​), RETURN_TYPE, POLICY>::fn.

  • A guide function used to match overriders with the method:

auto BOOST_OPENMETHOD_NAME(NAME)_guide(...)
    -> ::boost::openmethod::method<
        BOOST_OPENMETHOD_NAME(NAME)(PARAMETERS...), RETURN_TYPE [, POLICY]>;
Note
The default value for POLICY is the value of BOOST_OPENMETHOD_DEFAULT_POLICY at the point <boost/openmethod/core.hpp> is included. Changing the value of this symbol has no effect after that point.

BOOST_OPENMETHOD_OVERRIDE

Synopsis

Defined in <boost/openmethod/macros.hpp>.

BOOST_OPENMETHOD_OVERRIDE(NAME, (PARAMETERS...), RETURN_TYPE) {
    // body
}

Description

BOOST_OPENMETHOD_OVERRIDE adds an overrider to a method.

The method is deduced from a call to a method guide function with the overrider’s arguments.

The macro creates several entities in the current scope.

  • A class template that acts as a container for the overriders of the methods called NAME:

template<typename...> BOOST_OPENMETHOD_OVERRIDERS(NAME);
  • A specialization of the container template for the overrider:

struct BOOST_OPENMETHOD_OVERRIDERS(NAME)<RETURN_TYPE(PARAMETERS...)> {
    static auto fn(PARAMETERS...) -> RETURN_TYPE;
    static auto has_next() -> bool;
    template<typename... Args>
    static auto next(typename... Args) -> RETURN_TYPE;
};

where:

  • fn is the overrider function.

  • has_next() returns true if a less specialized overrider exists.

  • next(Args…​ args) calls the next most specialized overrider via the pointer stored in the method’s next<fn> member variable.

Finally, the macro starts the definition of the overrider function:

auto BOOST_OPENMETHOD_OVERRIDERS(NAME)<RETURN_TYPE(PARAMETERS...)>::fn(
    PARAMETERS...) -> RETURN_TYPE

The block following the call to the macro is the body of the function.

BOOST_OPENMETHOD_INLINE_OVERRIDE

Synopsis

Defined in <boost/openmethod/macros.hpp>.

BOOST_OPENMETHOD_INLINE_OVERRIDE(NAME, (PARAMETERS...), RETURN_TYPE) {
    // body
}

Description

BOOST_OPENMETHOD_INLINE_OVERRIDE performs the same function as BOOST_OPENMETHOD_OVERRIDE, except that the overrider is defined inline.

BOOST_OPENMETHOD_REGISTER

Synopsis

Defined in <boost/openmethod/macros/register.hpp>.

BOOST_OPENMETHOD_REGISTER(TYPE);

Description

Creates a static instance of TYPE, using a unique generated name.

BOOST_OPENMETHOD_CLASSES

Synopsis

Defined in <boost/openmethod/macros.hpp>.

BOOST_OPENMETHOD_CLASSES(CLASSES...[, POLICY]);

Description

Register CLASSES in POLICY.

Note
The default value for POLICY is the value of BOOST_OPENMETHOD_DEFAULT_POLICY when <boost/openmethod/core.hpp> is included. Subsequently changing it has no retroactive effect.

This macro is a wrapper around use_classes; see its documentation for more details.

BOOST_OPENMETHOD_DEFAULT_POLICY

Description

The name of the default policy.

BOOST_OPENMETHOD_DEFAULT_POLICY is the default value for the Policy template parameter of method, use_classes, and other constructs defined in <boost/openmethod/core.hpp>. If it is not defined, ::boost::openmethod::policy::default_policy is used.

BOOST_OPENMETHOD_DEFAULT_POLICY can be defined by a program to change the default policy globally. Once <boost/openmethod/core.hpp> has been included, redefining the symbol has no effect. To override the default policy, proceed as follows:

  1. Include headers under boost/openmethod/policies/ as needed.

  2. Create a policy class, and set BOOST_OPENMETHOD_DEFAULT_POLICY.

  3. Include <boost/openmethod/core.hpp>.

BOOST_OPENMETHOD_NAME

Synopsis

Defined in <boost/openmethod/macros/name.hpp>.

#define BOOST_OPENMETHOD_NAME(NAME) boost_openmethod_#NAME

Description

Generate a long name from a short name, hopefully avoiding conflicts with user-defined names.

BOOST_OPENMETHOD_OVERRIDERS

Synopsis

Defined in <boost/openmethod/macros.hpp>.

#define BOOST_OPENMETHOD_OVERRIDERS(NAME)                                      \
    BOOST_PP_CAT(BOOST_OPENMETHOD_NAME(NAME), _overriders)

Description

BOOST_OPENMETHOD_OVERRIDERS expands to the name of the class template that contains the overriders for all the methods with a given name.

initialize

Synopsis

Defined in <boost/openmethod/compiler.hpp>.

namespace boost::openmethod {

template<class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
auto compiler<Policy>::initialize() -> /*unspecified*/;

}

Description

Initializes dispatch data for the methods registered in Policy. This function must be called before any calls to those methods, and after loading or unloading a dynamic library that adds classes, methods or overriders to Policy.

finalize

Synopsis

Defined in <boost/openmethod/compiler.hpp>.

namespace boost::openmethod {

template<class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
auto finalize() -> void;

}

Description

De-allocates the resources allocated by initialize for the Policy, including resources allocated by the facets in Policy. Resources are de-allocated in an arbitrary order. It is not necessary to call finalize between calls to initialize. It may be required to cooperate with memory leak detection tools.

type_id

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod {

using type_id = std::uintptr_t;

}

Description

type_id is an unsigned integer type used to identify types. It is wide enough to contain a pointer.

vptr_type

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod {

using vptr_type = const /*unspecified*/ *;

}

Description

vptr_type is the type of a pointer to a v-table.

method

Synopsis

namespace boost::openmethod {

template<
    typename Method, typename ReturnType,
    class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
class method;

template<typename Name, typename... Parameters, typename ReturnType, class Policy>
class method<Name(Parameters...), ReturnType, Policy> {
  public:
    using function_type = ReturnType (*)(CallParameters...);

    auto operator()(CallParameters... args) const -> ReturnType;

    static method fn;

    template<auto... Functions>
    struct override;

    template<auto Overrider>
    static function_type next;

  private:
    method();
    method(const method&) = delete;
    method(method&&) = delete;
    ~method();
};

}

Description

method implements an open-method that takes a parameter list - Parameters - and returns a ReturnType. Name can be any type. Its purpose is to make it possible to have multiple methods with the same signature. Typically, Name is a class whose name reflects the method’s purpose.

Parameters must contain at least one virtual parameter, i.e. a parameter that has a type in the form virtual_ptr<T, Policy> or virtual_<T>. The dynamic types of the virtual arguments (the arguments corresponding to virtual parameters in the method’s signature) are taken into account to select the overrider to call.

A method is attached to a Policy, which influences several parts of the dispatch mechanism - for example, how to obtain a v-table pointer for an object, how to report errors, whether to perform sanity checks, etc.

Members

constructor
method();

Add the method to the list of methods registered in Policy.

The constructor is private. The only instance is the static member variable fn.

destructor
~method();

Remove the method from the list of methods registered in Policy.

operator()
auto operator()(CallParameters... args) const -> ReturnType;

Call the method with the arguments args.

CallParameters are the Parameters without the virtual_ decorators. Note that virtual_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:

  1. Form the set of all applicable overriders. An overrider is applicable if it can be called with the arguments passed to the method.

  2. If the set is empty, call the error handler (if present in the policy), then terminate the program with abort

  3. 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.

  4. If the resulting set contains only one overrider, call it.

  5. If the return type is a registered polymorphic type, remove all the overriders that return a less specific type than the others.

  6. If the resulting set contains only one overrider, call it.

  7. 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:

  1. If result is a virtual_ptr, get the pointer to the v-table from it.

  2. If a function named boost_openmethod_vptr that takes result and returns a vptr_type exists, call it.

  3. 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 a virtual_ptr<U>, where U is covariant with T. The Policy of the virtual_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_POLICY>
class virtual_ptr {
  public:
    static constexpr bool is_smart_ptr = /* see below */;
    using element_type = /* see below */;

    template<class Other> virtual_ptr(Other& other);
    template<class Other> virtual_ptr(const Other& other);
    template<class Other> virtual_ptr(Other&& other);

    template<class Other>
    static auto final(Other&& obj);

    auto get() const -> element_type*;
    auto operator->() const -> element_type*;
    auto operator*() const -> element_type&;
    auto pointer() const -> const Class*&;

    template<typename Other>
    auto cast() const -> virtual_ptr<Other, Policy>;
};

template<class Class>
virtual_ptr(Class&) -> virtual_ptr<Class, BOOST_OPENMETHOD_DEFAULT_POLICY>;

template<class Class>
inline auto final_virtual_ptr(Class& obj) -> virtual_ptr<
    Class, BOOST_OPENMETHOD_DEFAULT_POLICY>;

template<class Policy, class Class>
inline auto final_virtual_ptr(Class& obj) -> virtual_ptr<Class, Policy>;

template<class Left, class Right, class Policy>
auto operator==(
    const virtual_ptr<Left, Policy>& left,
    const virtual_ptr<Right, Policy>& right) -> bool;

template<class Left, class Right, class Policy>
auto operator!=(
    const virtual_ptr<Left, Policy>& left,
    const virtual_ptr<Right, Policy>& right) -> bool;

} // namespace boost::openmethod

Defined in <boost/openmethod/shared_ptr.hpp>:

namespace boost::openmethod {

template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using shared_virtual_ptr = virtual_ptr<std::shared_ptr<Class>, Policy>;

template<
    class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_shared_virtual(T&&... args)
    -> shared_virtual_ptr<Class, Policy>;

}

Defined in <boost/openmethod/unique_ptr.hpp>:

namespace boost::openmethod {

template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using unique_virtual_ptr = virtual_ptr<std::unique_ptr<Class>, Policy>;

template<
    class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_unique_virtual(T&&... args)
    -> unique_virtual_ptr<Class, Policy>;
}

Description

virtual_ptr is a wide pointer that combines a pointer to an object and a pointer to its v-table. The object pointer can be a plain pointer or a smart pointer. Specializations of virtual_traits are required for smart pointers. They are provided for std::unique_ptr and std::shared_ptr.

A plain virtual_ptr can be constructed from a reference, a smart pointer, or another virtual_ptr. A smart virtual_ptr can be constructed from a smart pointer or from a smart virtual_ptr. Usual conversions - from derived to base, and from non-const to const - are allowed.

virtual_ptr does not have a default constructor, nor a "null" state. In that respect, it behaves more like a reference than a pointer. The only reason why it is not called virtual_ref is to save the name for the day C++ will support smart references.

Members

is_smart_ptr
static constexpr bool is_smart_ptr;

true if Class is a smart pointer, false otherwise. The value is derived from virtual_traits<Class, Policy>: if it has a member template called rebind, Class is considered a smart pointer.

element_type
using element_type = std::conditional_t<
    is_smart_ptr, typename Class::element_type, Class>;

The class of the object pointed to.

constructors
template<class Other> virtual_ptr(Other& other);        // 1
template<class Other> virtual_ptr(const Other& other);  // 2
template<class Other> virtual_ptr(Other&& other);       // 3

(1), (2) If virtual_ptr uses a plain pointer, other must be a lvalue reference to an object of a registered class, or to a virtual_ptr (plain or smart). If virtual_ptr uses a smart pointer, other must be a reference to a smart pointer, or a smart virtual_ptr.

(3) Smart virtual_ptr only. Constructs a virtual_ptr from a smart pointer or a smart virtual_ptr. The (smart) object pointer is moved from other.

If other is also a virtual_ptr, the v-table pointer is copied from it. Otherwise, it is deduced from the object. The Policy must be the same for both virtual_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_POLICY>;

Non-members

virtual_shared_ptr
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using virtual_shared_ptr = virtual_ptr<std::shared_ptr<Class>, Policy>;

Convenience alias for virtual_ptr<std::shared_ptr<Class>, Policy>.

virtual_unique_ptr
template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
using virtual_unique_ptr = virtual_ptr<std::unique_ptr<Class>, Policy>;

Convenience alias for virtual_ptr<std::unique_ptr<Class>, Policy>.

final_virtual_ptr
template<class Policy, class Class>
inline auto final_virtual_ptr(Class&& obj);

template<class Class>
inline auto final_virtual_ptr(Class&& obj);

Utility functions, forwarding to virtual_ptr<Class, Policy>::final.

If Policy is not specified, BOOST_OPENMETHOD_DEFAULT_POLICY is used.

make_shared_virtual
template<
    class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_shared_virtual(T&&... args)
    -> shared_virtual_ptr<Class, Policy>;

Creates an object using std::make_shared and returns a virtual_shared_ptr to it. The v-table pointer is initialized from the the Policy::static_vptr for the class, which needs not be polymorphic.

make_unique_virtual
template<
    class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T>
inline auto make_unique_virtual(T&&... args)
    -> unique_virtual_ptr<Class, Policy>;

Creates an object using std::make_unique and returns a virtual_unique_ptr to it. The v-table pointer is initialized from the the Policy::static_vptr for the class, which needs not be polymorphic.

operator==
template<class Left, class Right, class Policy>
auto operator==(
    const virtual_ptr<Left, Policy>& left,
    const virtual_ptr<Right, Policy>& right) -> bool;

Compares two virtual_ptr objects for equality.

operator!=
template<class Left, class Right, class Policy>
auto operator!=(
    const virtual_ptr<Left, Policy>& left,
    const virtual_ptr<Right, Policy>& right) -> bool;

Compares two virtual_ptr objects for inequality.

virtual_traits

Synopsis

Defined in <boost/openmethod/core.hpp>.

namespace boost::openmethod {

template<class, class>
struct virtual_traits; // not defined

template<class Class, class Policy>
struct virtual_traits<..., Policy> {
    using virtual_type = ...;
    static auto peek(const T& arg) -> const ...&;
    template<typename Derived> static auto cast(T& obj) -> ...;
    template<class Other> using rebind = ...; // for smart virtual pointers
};

}

Description

Specializations of virtual_traits provide an interface for method and virtual_ptr to manipulate virtual arguments.

Specializations

Specializations are provided for:

  • virtual_ptr<T, Policy>

  • const virtual_ptr<T, Policy>&

  • T&

  • T&&

  • std::shared_ptr<T>: defined in <boost/openmethod/shared_ptr.hpp>

  • const std::shared_ptr<T>&: defined in <boost/openmethod/shared_ptr.hpp>

  • std::unique_ptr<T>: defined in <boost/openmethod/unique_ptr.hpp>

Members

virtual_type
using virtual_type = ...;

The class used for method selection. It must be registered in Policy.

For example, virtual_type in the following specializations are all Class:

  • virtual_traits<virtual_ptr<Class, Policy>>

  • virtual_traits<const virtual_ptr<std::shared_ptr<Class>&, Policy>

  • virtual_traits<Class&, Policy>

  • virtual_traits<const std::shared_ptr<Class>&, Policy>

peek
static auto peek(T arg) -> const ...&;

Returns a value for the purpose of obtaining a v-table pointer for arg.

For example, peek returns a const T& for a T&, a const T&, a T&&, and a std::shared_ptr<T>; and a const virtual_ptr<Class, Policy>& for a const virtual_ptr<Class, Policy>&.

cast
template<typename Derived>
static decltype(auto) cast(T& obj);

Casts argument obj to the type expected by an overrider.

For example, if a method takes a virtual_<Animal&>, an overrider for Cat& uses virtual_traits to cast a Animal& to a Cat&.

rebind
template<class Other> using rebind = ...;

For smart pointers only. Rebinds the smart pointer to a different type. For example, virtual_traits<std::shared_ptr<T>, Policy>::rebind<U> is std::shared_ptr<U>.

use_classes

Synopsis

Defined in <boost/openmethod/core.hpp>.

namespace boost::openmethod {

template<class... Classes, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
struct use_classes {
    use_classes();
    ~use_classes();
};

}

Usage:

use_classes<Classes...> some_unique_name; // at file scope

Description

use_classes, instantiated as a static object, registers Classes in Policy.

Classes potentially involved in a method definition, an overrider, or a method call must be registered via use_classes. A class may be registered multiple times. A class and its direct bases must be listed together in one or more instantiations of use_classes.

Virtual and multiple inheritance are supported, as long as they don’t result in a class lattice that contains repeated inheritance.

Note
The default value for Policy is the value of BOOST_OPENMETHOD_DEFAULT_POLICY when <boost/openmethod/core.hpp> is included. Subsequently changing it has no retroactive effect.

Members

constructor
use_classes();

Registers Classes and their inheritance relationships in Policy.

destructor
~use_classes();

Removes Classes and their inheritance relationships from Policy.

virtual_

Synopsis

Defined in <boost/openmethod/core.hpp>.

namespace boost::openmethod {

template<typename T>
struct virtual_;

}

Description

Marks a formal parameter of a method as virtual. Requires a specialization of virtual_traits for T and the Policy of the method. Specializations for T&, T&&, std::unique_ptr<T>, std::shared_ptr<T> and const std::shared_ptr<T>& are provided. See the documentation of virtual_traits for more information.

with_vptr

Synopsis

Defined in <boost/openmethod/core.hpp>.

namespace boost::openmethod {

template<class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY>
class with_vptr {
  protected:
    with_vptr();
    ~with_vptr();
    friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
};

template<class Class, class Base, class... MoreBases>
class with_vptr {
  protected:
    with_vptr();
    ~with_vptr();
    friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type;
        // if sizeof(MoreBases...) > 0
};

} // namespace boost::openmethod

Description

with_vptr is a CRTP class template that embeds and manages a vptr across a class hierarchy.

If Class has no Bases, with_vptr adds a boost_openmethod_vptr private member to Class. In either case, it sets the vptr to the v-table of Class from Policy. It also creates a boost_openmethod_vptr friend function that takes a a const Class& and returns the embedded vptr.

If Class has has more than one base, the boost_openmethod_vptr friend function is also created. It returns one of the embedded vptrs (it doesn’t matter which one, as they all have the same value). This is to resolve ambiguities

As part of its implementation, with_vptr may also declare one or two free functions (boost_openmethod_policy and boost_openmethod_bases) at certain levels of the hierarchy.

Members

constructor
with_vptr();

Sets the vptr to the v-table for Class, obtained from Policy. If Policy contains indirect_vptr, an additional level of indirection is added, thus preserving the validity of the pointer across calls to initialize.

destructor
~with_vptr();

For each Base, sets the vptr to the v-table for that base.

Free Functions
auto boost_openmethod_vptr(const Class& obj) -> vptr_type;

Returns the vptr embedded in obj.

abstract_policy

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct abstract_policy {};

}

Description

abstract_policy is a required base class for a policy. It makes it possible for meta-functions such as use_classes to discriminate between user classes and the (optional) policy class.

domain

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

template<class Policy>
struct domain {
    template<class Class> static vptr_type static_vptr;
    // unspecified members
};

}

Description

domain is a registry of classes and methods registered in a Policy, and their dispatch tables.

Members

static_vptr
template<class Class>
static vptr_type static_vptr;

Contains the pointer to the v-table for Class. Set by initialize.

basic_policy

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod {

namespace policies {

template<class Policy, class... Facets>
struct basic_policy : abstract_policy, domain<Policy>, Facets... {
    template<class Facet>
    static constexpr bool has_facet = /*unspecified*/;

    template<class NewPolicy>
    using fork = /*unspecified*/;

    template<class... MoreFacets>
    using add = /*unspecified*/;

    template<class Base, class Facet>
    using replace = /*unspecified*/;

    template<class Base>
    using remove = /*unspecified*/;
};

struct release : basic_policy<release, ...> {};

struct debug : release::add<...> {};

} // policies

#ifdef NDEBUG
using default_policy = policies::release;
#else
using default_policy = policies::debug;
#endif

} // boost::openmethod

Description

basic_policy implements a policy, which consists of a a collection of methods, classes, dispatch data, and facets, which specify how to obtain a pointer to a v-table from an object, how to report errors, whether to perform runtime sanity checks, etc.

Some of these functionalities require static variables local to the policy. Forthis reason, basic_policy uses the CRTP pattern to provide ensure that two different policies - and the facets they contain - get their own copies of the static state.

Members

has_facet
template<class Facet>
static constexpr bool has_facet;

Evaluates to true if Policy contains Facet.

fork
template<class NewPolicy>
using fork;

Creates a new policy from an existing one. NewPolicy, and the facets it contains, do not share static variables with the original Policy. The new policy does not retain any knowledge of the classes and methods registered in the original.

add
template<class... MoreFacets>
using add;

Creates a new policy by adding MoreFacets to the original policy’s collection of facets. The original policy and the new one share static variables.

replace
template<class Base, class NewFacet>
using replace;

Creates a new policy by replacing the facet in Policy that derives from Base with NewFacet. It is not an error if policy does not contain such a facet; in that case, the new policy contains the same facet as the original one.

The original policy and the new one share static variables.

remove
template<class Base>
using remove;

Creates a new policy by removing the facet in Policy that derives from Base. It is not an error if policy does not contain such a facet; in that case, the new policy contains the same facet as the original one.

The original policy and the new one share static variables.

Non-members

release
struct release;

A policy that contains facet implementations std_rtti, fast_perfect_hash, vptr_vector and vectored_error_handler.

debug
struct debug;

The release policy with additional facet implementations runtime_checks, basic_error_output and basic_trace_output.

Note
debug extends release but it does not a fork it. Both policies use the same domain.
default_policy

An alias for release if NDEBUG is defined, and for debug otherwise.

facet

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct facet {
    static auto finalize() -> void;
};

} // boost::openmethod::policies

Description

facet is the base class of all facets. It provides an empty finalize static function which can be overriden (via shadowing) by derived classes.

Members

finalize
static auto finalize() -> void;

Does nothing.

rtti

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct rtti : facet {};

} // boost::openmethod::policies

Description

The rtti facet provides type information for classes and objects, implements downcast in presence of virtual inheritance, and writes descriptions of types to an ostream-like object.

Requirements

static_type
template<class Class>
static auto static_type() -> type_id;

Returns a type_id for Class.

dynamic_type
template<class Class>
static auto dynamic_type(const Class& obj) -> type_id;

Returns a type_id for an object’s dynamic type.

type_name
template<typename Stream>
static auto type_name(type_id type, Stream& stream) -> void;

Writes a description of type to stream.

This requirement is optional. rtti provides a default implementation that writes typeid({type}) to stream.

type_index
static auto type_index(type_id type) -> /* unspecified */;

Returns a unique key for type. Required only for RTTI systems that assign more than one type "identifiers" to a type. For example, standard RTTI allows implementations to have multiple instances of std::type_info for the same type.

dynamic_cast_ref
template<typename D, typename B>
static auto dynamic_cast_ref(B&& obj) -> D;

Casts obj to D. Required only if using virtual inheritance.

std_rtti

Synopsis

Defined in <boost/openmethod/policies/std_rtti.hpp>.

namespace boost::openmethod::policies {

struct std_rtti : rtti {
    template<class Class>
    static auto static_type() -> type_id;

    template<class Class>
    static auto dynamic_type(const Class& obj) -> type_id;

    template<typename Stream>
    static auto type_name(type_id type, Stream& stream) -> void;

    static auto type_index(type_id type) -> std::type_index;

    template<typename D, typename B>
    static auto dynamic_cast_ref(B&& obj) -> D;
};

} // boost::openmethod::policies

Description

std_rtti is an implementation of the rtti facet that uses standard RTTI.

Members

static_type
template<class Class>
static auto static_type() -> type_id;

Return the address of Class’s `type_info, cast to a type_id.

dynamic_type
template<class Class>
static auto dynamic_type(const Class& obj) -> type_id;

Return the address of obj's type_info, cast to a type_id.

type_name
template<typename Stream>
static auto type_name(type_id type, Stream& stream) -> void;

Write the demangled name of the class identified by type to stream. Execute stream << reinterpret_cast<const std::type_info*>(type)→name().

type_index
static auto type_index(type_id type) -> /*unspecified*/;

Return std::type_index(reinterpret_cast<const std::type_info>(type)).

The function is required because C++ does not guarantee that there is a single instance of std::type_info for each specific type.

dynamic_cast_ref
template<typename Derived, typename Base>
static auto dynamic_cast_ref(Base&& obj) -> Derived;

Cast obj using the dynamic_cast operator.

deferred_static_rtti

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct deferred_static_rtti : rtti {};

}

Description

deferred_static_rtti is a facet that defers collection of static type ids.

Some custom RTTI systems rely on static constructors to assign type ids. OpenMethod itself relies on static constructors to register classes, methods and overriders, calling the static_type function from the rtti facet in the process. This can result in collecting the type ids before they have been initialized. Adding this facet to a policy moves the collection of type ids to initialize.

minimal_rtti

Synopsis

struct minimal_rtti : rtti {
    template<typename Class>
    static auto static_type() -> type_id;
};

Description

minimal_rtti is an implementation of the rtti facet that only uses static type information.

minimal_rtti provides the only function strictly required for the rtti facet.

This facet can be used in programs that call methods solely via virtual_ptrs created with the "final" constructs. Virtual inheritance is not supported. Classes are not required to be polymorphic.

Members

static_type
template<class Class>
static auto static_type() -> type_id;

Returns the address of a local static char variable, cast to type_id.

extern_vptr

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct extern_vptr : facet {};

}

Description

extern_vptr is a facet that stores and returns pointers to v-tables for registered classes.

Requirements

register_vptrs
template<typename ForwardIterator>
auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;

ForwardIterator is a forward iterator over a range of objects that contain information about the type ids and the vptr of a registered class. They have the following member functions:

auto type_id_begin() const -> type_id_forward_iterator;
auto type_id_end() const -> type_id_forward_iterator;
auto vptr() const -> const vptr_type&;

type_id_begin and type_id_end return iterators delimiting a range of `type_id`s for a class.

vptr returns a reference to a static variable containing a pointer to the v-table for a the class. Its value is set by initialize. While the value of the variable changes with each call to initialize, the variable itself remains the same.

indirect_vptr

Synopsis

struct indirect_vptr : facet {};

Description

indirect_vptr is a facet that makes virtual_ptrs and with_vptr use pointers to pointers to v-tables, instead of straight pointers. As a consequence, they remain valid after a call to initialize.

Requirements

None. The facet is its own implementation.

vptr_vector

Synopsis

Defined in <boost/openmethod/policies/vptr_vector.hpp>.

namespace boost::openmethod::policies {

template<class Policy, typename Facet = void>
class vptr_vector : Base {
  public:
    template<typename ForwardIterator>
    static auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;

    template<class Class>
    static auto dynamic_vptr(const Class& arg) -> const vptr_type&;
};

}

Description

vptr_vector is an implementation or external_vptr that keeps the pointers to the v-tables in a std::vector. If UseIndirectVptrs is indirect_vptr, stores pointers to pointers to the v-tables.

If Facet is specified, it must be either void or indirect_vptr. void indicates that straight pointers to v-tables should be stored.

Policy is the policy containing the facet.

Members

register_vptrs
template<typename ForwardIterator>
auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;

Stores the pointers to v-tables in a vector, indexed by the (possibly hashed) type_id`s of the classes registered in `Policy.

If Policy contains a type_hash facet, call its hash_initialize function, and uses it to convert the type_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 facet, uses it to convert the result to an index; otherwise, uses the type_id as the index.

vptr_map

Synopsis

namespace boost::openmethod::policies {

### Synopsis

template<class Policy, class Facet = void, class Map = /* unspecified */>
class vptr_map : extern_vptr, Facet /* if not void */ {
  public:
    template<typename ForwardIterator>
    static auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;

    template<class Class>
    static auto dynamic_vptr(const Class& arg) -> const vptr_type&;
};

}

Description

vptr_map is an implementation of `external_vptr that stores the pointers to the v-tables in a map.

Policy is the policy containing the facet.

If Facet is specified, it must be either void or indirect_vptr. void indicates that straight pointers to v-tables should be stored.

Map is an AssociativeContainer that maps type_ids to vptr_types if Facet is void, or pointers vptr_types if Facet is indirect_vptr.

Members

register_vptrs
template<typename ForwardIterator>
auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void;

Stores the pointers to v-tables in a Map.

dynamic_vptr
template<class Class>
auto dynamic_vptr(const Class& object) -> const vptr_type&;

Returns a pointer to the v-table for object (by reference).

If Policy contains the runtime_checks facet, checks if Class is registered. If it is not, and Policy contains a error_handler facet, calls its error function; then calls abort.

type_hash

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct type_hash : facet {};

} // boost::openmethod::policies

Description

type_hash is a facet that provides a hash function for a fixed set of type_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 facet, hash_type_id checks that type corresponds to a registered class. If not, it reports a unknown_class_error using the policy’s error_handler facet, if present, then calls abort.

hash_initialize
template<typename ForwardIterator>
auto hash_initialize(ForwardIterator first, ForwardIterator last) -> Report;

Finds factors M and S such that hash_type_id is a collision-free hash function.

If no such factors cannot be found, hash_initialize reports a hash_search_error using the policy’s error_handler facet, if present, the calls abort.

If the policy has a trace_output facet, hash_initialize uses it to write a summary of the search.

error_handler

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct error_handler;

}

Defined in <boost/openmethod/types.hpp>.

namespace boost::openmethod {

struct openmethod_error {};

struct not_implemented_error : openmethod_error {
    type_id method;
    std::size_t arity;
    static constexpr std::size_t max_types = 16;
    type_id types[max_types];
};

struct unknown_class_error : openmethod_error {
    type_id type;
};

struct hash_search_error : openmethod_error {
    std::size_t attempts;
    std::size_t buckets;
};

struct type_mismatch_error : openmethod_error {
    type_id type;
};

} // boost::openmethod::policies

Description

error_handler is a facet that handles errors.

When an error is encountered, either during initialize or method dispatch, the program is terminated via a call to abort. If this facet is present in the policy, its error function is called with an error object. It can prevent termination by throwing an exception.

Requirements

Implementations of error_handler must provide the following functions:

error
static auto error(const T&) -> void;

vectored_error_handler

Synopsis

Defined in <boost/openmethod/policies/vectored_error_handler.hpp>.

namespace boost::openmethod::policies {

template<class Policy>
class vectored_error_handler : public error_handler {
  public:
    using error_variant = std::variant<
        openmethod_error, not_implemented_error, unknown_class_error,
        hash_search_error, type_mismatch_error, static_slot_error,
        static_stride_error>;
    using function_type = std::function<void(const error_variant& error)>;

    template<class Error>
    static auto error(const Error& error) -> void;
    static auto set_error_handler(error_handler_type handler) -> function_type;
};

}

Description

vectored_error_handler is an implementation of error_handler that calls a std::function to handle the error.

Members

error
template<class Error>
static auto error(const Error& error) -> void;

Calls the function last set via set_error_handler or, if it was never called, and if Policy contains an error_output facet, use it to print a description of error.

error
static auto set_error_handler(function_type handler) -> function_type;

Sets handler as the function to call in case of error.

throw_error_handler

Synopsis

Defined in <boost/openmethod/policies/throw_error_handler.hpp>.

namespace boost::openmethod::policies {

struct throw_error_handler : error_handler {
    template<class Error>
    [[noreturn]] static auto error(const Error& error) -> void;
};

} // boost::openmethod::policies

Description

throw_error_handler is an implementation of the error_handler facet that throws the error as an exception.

Members

error
template<class Error>
[[noreturn]] static auto error(const Error& error) -> void;

Throws error.

error_output

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct error_output : facet {};

} // boost::openmethod::policies

Description

error_output is a facet that provides a stream for writing error messages.

Requirements

error_stream
static RestrictedOutputStream error_stream;

A static variable that satisfies the requirements of RestrictedOutputStream.

basic_error_output

Synopsis

Defined in <boost/openmethod/policies/basic_error_output.hpp>.

namespace boost::openmethod::policies {

template<class Policy, typename Stream = /*unspecified*/>
struct basic_error_output : error_output {
    static Stream error_stream;
};

}

Description

basic_error_output is an implementation of error_output that writes error messages to a RestrictedOutputStream.

Members

error_stream
Stream  error_stream;

Initialized by the default constructor of Stream. It is the responsibility of the program to initializate it if needed, e.g., for a std::ofstream, to open it.

trace_output

Synopsis

Defined in <boost/openmethod/policies/basic_policy.hpp>.

namespace boost::openmethod::policies {

struct trace_output : facet {};

}

Description

trace_output is a facet used to write trace messages.

initialize can be directed to describe the classes and methods in a policy, and how the dispatch tables are built, by including this facet in the policy, and setting trace_enabled to true. The content and the format of the description is not documented, beyond the guarantee that it provides an exhaustive account of table construction, and may change between major, minor and patch versions.

Requirements

trace_enabled
static bool trace_enabled;

true if tracing is enabled, false otherwise.

trace_stream
static RestrictedOutputStream trace_stream;

A static variable that satisfies the requirements of RestrictedOutputStream.

basic_trace_output

Synopsis

Defined in <boost/openmethod/policies/basic_trace_output.hpp>.

namespace boost::openmethod::policies {

template<class Policy, typename Stream = /*unspecified*/>
struct basic_trace_output : trace_output {
    static bool trace_enabled;
    static Stream trace_stream;
};

}

Description

basic_error_output is an implementation of trace_output that writes error messages to a RestrictedOutputStream.

Members

trace_enabled
static bool trace_enabled;

Set to true if environment variable BOOST_OPENMETHOD_TRACE is set to 1.

trace_stream
static Stream trace_stream;

Initialized by the default constructor of Stream. It is the responsibility of the program to prepare it for output if needed, e.g., for a std::ofstream, to open it.

RestrictedOutputStream

Description

RestrictedOutputStream is a concept describing a std::ostream-like class with a reduced set of operations.

While convenient, std::ostream and its implementations constitute a sizeable piece of code, which may make it unsuitable for certain applications. OpenMethod uses a small subset of the operations supported by std::ostream. By default, the library uses a lightweight implementation based on the C stream functions.

Implementations of RestrictedOutputStream provide the following functions:

Name Description

RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const char* str)

Write a null-terminated string str to os

RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const std::string_view& view)

Write a view to `os

RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const void* value)

Write a representation of a pointer to os

RestrictedOutputStream& operator<<(RestrictedOutputStream& os, std::size_t value)

Write an unsigned integer to os