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
andhas_next
members, and a static function calledfn
. The block following the macro is the body of thefn
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";
}