Headers and Namespaces

Most real-life programs will be organized in multiple files and multiple namespaces. OpenMethod interacts with headers and namespaces naturally, if using-directives are avoided. In that case, there are a few things to be aware of.

Let’s break the Animals example into headers and namespaces. First we put Animal in its own header and namespace:

// animal.hpp

#ifndef ANIMAL_HPP
#define ANIMAL_HPP

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

namespace animals {

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

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

} // namespace animals

#endif // ANIMAL_HPP

BOOST_OPENMETHOD can be placed in a header file. It adds several constructs to the current namespace:

  • It declares (but does not define) a struct named after the method.

  • It declares (but does not define) a guide function. It is also named after the method, and it has the same signature (with the virtual_ decorators stripped). It is used to match methods and overriders. It is never defined and it is "called" only in a non-evaluated context.

  • It defines an inline function with the same name and signature as the method (with the virtual_ decorators stripped).

Next, let’s implement the Cat class, and a derived class, Cheetah, in the felines namespace:

// felines.hpp

#ifndef FELINES_HPP
#define FELINES_HPP

#include "animal.hpp"

namespace felines {

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

struct Cheetah : Cat {
    using Cat::Cat;
};

} // namespace felines

#endif // FELINES_HPP
// cat.cpp

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

#include "cat.hpp"

using boost::openmethod::virtual_ptr;

namespace felines {

BOOST_OPENMETHOD_CLASSES(animals::Animal, Cat, Cheetah);

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

BOOST_OPENMETHOD_OVERRIDE(
    poke, (std::ostream & os, virtual_ptr<Cheetah> cat), void) {
    BOOST_OPENMETHOD_OVERRIDER(
        poke, (std::ostream & os, virtual_ptr<Cat> dog), void)::fn(os, cat);
    os << " and runs away";
}

} // namespace felines

BOOST_OPENMETHOD_CLASSES should be placed in an implementation file. It can also go in a header file, but this wastes space, as the same registrar will be created in every translation unit that includes the header. It doesn’t matter which namespace the macro is called in. It can be used with any class name in scope, or with qualified names.

BOOST_OPENMETHOD_OVERRIDE uses the guide function declared by BOOST_OPENMETHOD to locate a method that can be called with the same arguments as the overrider itself. It "calls" the guide function in a non-evaluated context, passing it a std::ostream& and a virtual_ptr<Cat>. The return type of the guide function is the method to add the overrider to. Exactly one guide function must match. The normal rules of overload resolution apply. In that case, the guide function is found via argument dependant lookup (ADL).

The macro adds several constructs to the current namespace:

  • It declares (but does not define) a struct template with one type parameter, named after the method. The template acts like a container for overriders.

  • It specializes the template for the signature of the overrider. Inside the struct, it defines the next and has_next members, and a static function called fn. The block following the macro is the body of the fn function.

It follows that BOOST_OPENMETHOD_OVERRIDE should be placed in an implementation file. BOOST_OPENMETHOD_INLINE_OVERRIDE works like BOOST_OPENMETHOD_OVERRIDE, but it defines the fn function as inline, so it can be used in a header file.

The overrider for Cats can be accessed in the same translation unit, after it has been defined, using the BOOST_OPENMETHOD_OVERRIDER macro. It expands to the specialization of the overrider container for the overrider’s signature. We call the static fn function to call the overrider.

The Cheetah overrider calls the specific overrider for Cat, for illustration purpose. It is usually better to call next instead.

Let’s implement the Dog class, in the canines namespace. This time we want the overrider to be accessible in other translation units. We can declare an overrider with BOOST_OPENMETHOD_DECLARE_OVERRIDER, without actually defining the static function fn just yet.

#ifndef CANINES_HPP
#define CANINES_HPP

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

#include "animal.hpp"

namespace canines {

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

BOOST_OPENMETHOD_DECLARE_OVERRIDER(
    poke, (std::ostream & os, boost::openmethod::virtual_ptr<Dog> dog), void);

} // namespace canines

#endif // CANINES_HPP

Unlike function declarations, which can occur multiple times in a TU, an overrider declaration cannot. For example, this is illegal:

BOOST_OPENMETHOD_DECLARE_OVERRIDER(
    poke, (std::ostream&, virtual_ptr<Dog>), void);

BOOST_OPENMETHOD_DECLARE_OVERRIDER(
    poke, (std::ostream&, virtual_ptr<Dog>), void);

Now we use BOOST_OPENMETHOD_DEFINE_OVERRIDER to define the overrider:

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

#include "dog.hpp"

namespace canines {

BOOST_OPENMETHOD_CLASSES(animals::Animal, Dog);

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

} // namespace canines

Let’s look at the main program now. It derives Bulldog from Dog and provides an overrider for the new class:

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

#include "animal.hpp"
#include "cat.hpp"
#include "dog.hpp"

using boost::openmethod::virtual_ptr;

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

BOOST_OPENMETHOD_CLASSES(canines::Dog, Bulldog);

BOOST_OPENMETHOD_OVERRIDE(
    poke, (std::ostream & os, virtual_ptr<Bulldog> dog), void) {
    canines::BOOST_OPENMETHOD_OVERRIDER(
        poke, (std::ostream & os, virtual_ptr<canines::Dog> dog),
        void)::fn(os, dog);
    os << " and bites back";
}

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

    std::unique_ptr<animals::Animal> felix(new felines::Cat("Felix"));
    std::unique_ptr<animals::Animal> azaad(new felines::Cheetah("Azaad"));
    std::unique_ptr<animals::Animal> snoopy(new canines::Dog("Snoopy"));
    std::unique_ptr<animals::Animal> hector(new Bulldog("Hector"));

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

    poke(std::cout, *azaad); // Azaad hisses and runs away
    std::cout << ".\n";

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

    poke(std::cout, *hector); // Hector barks and bites
    std::cout << ".\n";

    return 0;
}

Again ADL plays a role: it helps the overrider (and main) to locate the poke method.

This example is the "happy scenario", where namespaces are used conservatively.

The OVERRIDE macros don’t interact well with using directives. For example this code:

using namespace animals;
using namespace canines;
using namespace felines;

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

BOOST_OPENMETHOD_CLASSES(Dog, Bulldog);

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

…​will fail to compile, with an error like "reference to 'poke_boost_openmethod_overriders' is ambiguous". That is because the overrider containers exist in both the canines and felines namespaces, with the same name.

Finally, the names passed as first arguments to the BOOST_OPENMETHOD and BOOST_OPENMETHOD_OVERRIDE macros must be identifiers. Qualified names are not allowed. Consider:

using animals::Animal;

namespace app_specific_behavior {

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

} // namespace app_specific_behavior

BOOST_OPENMETHOD_OVERRIDE(
    meet, (std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
    os << "ignore";
}

Here, the guide function cannot be found, even via ADL. We get an error like "use of undeclared identifier 'meet_boost_openmethod_guide'". How do we solve this? We might be tempted to use a qualified name: app_specific_behavior::meet:

BOOST_OPENMETHOD_OVERRIDE(
    app_specific_behavior::meet,
    (std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
    os << "ignore";
}

But BOOST_OPENMETHOD_OVERRIDE also uses the name to derive the overrider container’s name, using preprocessor token pasting, resulting in an invalid declaration error.

We need to do is to make BOOST_OPENMETHOD_OVERRIDE "see" the guide function. Its name is returned by macro BOOST_OPENMETHOD_GUIDE(NAME). We can use a using-declaration to bring the guide function into the current scope:

using app_specific_behavior::BOOST_OPENMETHOD_GUIDE(meet);

BOOST_OPENMETHOD_OVERRIDE(
    meet, (std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
    os << "ignore";
}