Alternatives to virtual_ptr

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

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

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

struct Cat : Animal {};

using boost::openmethod::virtual_;

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

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

BOOST_OPENMETHOD_CLASSES(Animal, Cat);

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

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

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

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

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

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

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

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

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

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

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

BOOST_OPENMETHOD_CLASSES(Animal, Cat);

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

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

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

#include <boost/openmethod/inplace_vptr.hpp>

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

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

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

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

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

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

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