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";