namespaces and headers tutorial

This commit is contained in:
Jean-Louis Leroy
2025-04-16 17:50:59 -04:00
parent 02495365a2
commit e1513fbae9
16 changed files with 384 additions and 16 deletions

View File

@@ -15,8 +15,6 @@ BOOST_OPENMETHOD(NAME, (PARAMETERS...), RETURN_TYPE [, POLICY]);
Declares a method.
### Effects
The macro expands to several constructs:
* A `struct` forward declaration that acts as the method's identifier:
@@ -38,6 +36,8 @@ auto BOOST_OPENMETHOD_NAME(NAME)_guide(...)
BOOST_OPENMETHOD_NAME(NAME)(PARAMETERS...), RETURN_TYPE [, POLICY]>;
```
NOTE: `NAME` must be an *identifier*. Qualified names are not allowed.
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.

View File

@@ -17,3 +17,5 @@ BOOST_OPENMETHOD_INLINE_OVERRIDE(NAME, (PARAMETERS...), RETURN_TYPE) {
`BOOST_OPENMETHOD_INLINE_OVERRIDE` performs the same function as
`BOOST_OPENMETHOD_OVERRIDE`, except that the overrider is defined inline.
NOTE: `NAME` must be an *identifier*. Qualified names are not allowed.

View File

@@ -57,3 +57,5 @@ auto BOOST_OPENMETHOD_OVERRIDERS(NAME)<RETURN_TYPE(PARAMETERS...)>::fn(
```
The block following the call to the macro is the body of the function.
NOTE: `NAME` must be an *identifier*. Qualified names are not allowed.

View File

@@ -1,9 +1,9 @@
## 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
We can use overrider containers to grant friendship to a specific overrider, or
to all the overriders of a method. The name of the container template is
returned by `BOOST_OPENMETHOD_OVERRIDERS`. The template argument for a
specialization is the signature of the overrider. For example, the overrider of
`poke` for `Cat` is:
@@ -20,20 +20,12 @@ We can thus grant friendship to all the overriders of `poke`:
include::{examplesdir}/friendship.cpp[tag=friend_all]
----
Be aware, though, that the overriders of _any_ method called `poke` - even with
a different signature - are granted friendship.
Be aware, though, that the overriders of _any_ method called `poke` - with any
signature - are granted friendship.
We can also grant friendship to individual overriders:
We can also befriendto individual overriders:
[source,c++]
----
include::{examplesdir}/friendship.cpp[tag=friend]
----
// If the overriders exist in a different namespace, we must take into account that
// the overriders template is declared in the current namespace.
// [source,c++]
// ----
// include::{examplesdir}/friendship_across_namespaces.cpp[tag=friend]
// ----

171
doc/headers_namespaces.adoc Normal file
View File

@@ -0,0 +1,171 @@
## Headers and Namespaces
Most real-life programs will be organized in multiple files and multiple
namespaces. OpenMethod interacts with headers and namespaces very naturally,
unless 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:
[source,c++]
----
include::{examplesdir}/headers_namespaces/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, in the `felines` namespace:
[source,c++]
----
include::{examplesdir}/headers_namespaces/cat.hpp[]
----
[source,c++]
----
include::{examplesdir}/headers_namespaces/cat.cpp[]
----
`BOOST_OPENMETHOD_CLASSES` should be placed in an implementation file. It can
also go in a header file, but this wastes space, as multiple copies of the same
class data will be created. It doesn't matter which namespace the macro is
called in. It can take 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 this
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.
Let's implement the `Dog` class, in the `canines` namespace:
[source,c++]
----
include::{examplesdir}/headers_namespaces/dog.hpp[]
----
[source,c++]
----
include::{examplesdir}/headers_namespaces/dog.cpp[]
----
`BOOST_OPENMETHOD_INLINE_OVERRIDE` works like `BOOST_OPENMETHOD_OVERRIDE`, but
it can be used in a header file, because it defines the `fn` function inline.
Let's look at the main program now. It derived `Bullgod` from `Dog` and provides
an overrider for the new class:
[source,c++]
----
include::{examplesdir}/headers_namespaces/main.cpp[]
----
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:
```c++
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 canides and felides 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:
```c++
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`:
```c++
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. And the result will be an
invalid declaration.
All we need to do is to make `BOOST_OPENMETHOD_OVERRIDE` "see" the guide
function. Its name is produced by macro `BOOST_OPENMETHOD_GUIDE(NAME)`. Thus we
can use a using-declaration to bring the guide function into the current scope:
```c++
using app_specific_behavior::BOOST_OPENMETHOD_GUIDE(meet);
BOOST_OPENMETHOD_OVERRIDE(
meet, (std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
os << "ignore";
}
```

View File

@@ -8,6 +8,7 @@
include::hello_world.adoc[]
include::multiple_dispatch.adoc[]
include::headers_namespaces.adoc[]
include::friendship.adoc[]
include::performance.adoc[]
include::smart_pointers.adoc[]

View File

@@ -117,3 +117,5 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(dl_shared Boost::openmethod)
add_test(NAME dlopen COMMAND dl_main)
endif()
add_subdirectory(headers_namespaces)

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2018-2024 Jean-Louis Leroy
# Distributed under the Boost Software License, Version 1.0.
# See accompanying file LICENSE_1_0.txt
# or copy at http://www.boost.org/LICENSE_1_0.txt)
add_executable(headers_namespaces main.cpp cat.cpp dog.cpp)
target_link_libraries(headers_namespaces Boost::openmethod)
add_test(NAME headers_namespaces COMMAND headers_namespaces)

View File

@@ -0,0 +1,22 @@
// 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&, virtual_ptr<Animal>), void);
} // namespace animals
#endif // ANIMAL_HPP

View File

@@ -0,0 +1,17 @@
// cat.cpp
#include <iostream>
#include <boost/openmethod.hpp>
#include "cat.hpp"
namespace felines {
BOOST_OPENMETHOD_CLASSES(animals::Animal, Cat);
BOOST_OPENMETHOD_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Cat> cat), void) {
os << cat->name << " hisses";
}
}

View File

@@ -0,0 +1,16 @@
// felines.hpp
#ifndef FELINES_HPP
#define FELINES_HPP
#include "animal.hpp"
namespace felines {
struct Cat : animals::Animal {
using Animal::Animal;
};
}
#endif // FELINES_HPP

View File

@@ -0,0 +1,10 @@
#include <iostream>
#include<boost/openmethod.hpp>
#include "dog.hpp"
namespace canines {
BOOST_OPENMETHOD_CLASSES(animals::Animal, Dog);
}

View File

@@ -0,0 +1,22 @@
#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_INLINE_OVERRIDE(
poke, (std::ostream & os, virtual_ptr<Dog> dog), void) {
os << dog->name << " barks";
}
} // namespace canines
#endif // CANINES_HPP

View File

@@ -0,0 +1,38 @@
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
#include "animal.hpp"
#include "cat.hpp"
#include "dog.hpp"
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) {
next(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> 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, *snoopy); // Snoopy barks
std::cout << ".\n";
poke(std::cout, *hector); // Hector barks and bites
std::cout << ".\n";
return 0;
}

View File

@@ -0,0 +1,23 @@
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
#include "animal.hpp"
#include "cat.hpp"
#include "dog.hpp"
using animals::Animal;
namespace app_specific_behavior {
BOOST_OPENMETHOD(
meet, (std::ostream&, virtual_ptr<Animal>, virtual_ptr<Animal>), void);
}
using app_specific_behavior::BOOST_OPENMETHOD_GUIDE(meet);
BOOST_OPENMETHOD_OVERRIDE(
meet, (std::ostream& os, virtual_ptr<Animal>, virtual_ptr<Animal>), void) {
os << "ignore";
}

View File

@@ -0,0 +1,42 @@
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/compiler.hpp>
#include "animal.hpp"
#include "cat.hpp"
#include "dog.hpp"
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";
}
auto main() -> int {
boost::openmethod::initialize();
std::unique_ptr<animals::Animal> felix(new Cat("Felix"));
std::unique_ptr<animals::Animal> snoopy(new Dog("Snoopy"));
std::unique_ptr<animals::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";
return 0;
}