From e1513fbae9c0169d19a5a0fe0598bc1dc25fab6d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Wed, 16 Apr 2025 17:50:59 -0400 Subject: [PATCH] namespaces and headers tutorial --- doc/BOOST_OPENMETHOD.adoc | 4 +- doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc | 2 + doc/BOOST_OPENMETHOD_OVERRIDE.adoc | 2 + doc/friendship.adoc | 20 +- doc/headers_namespaces.adoc | 171 ++++++++++++++++++ doc/tutorial.adoc | 1 + examples/CMakeLists.txt | 2 + examples/headers_namespaces/CMakeLists.txt | 8 + examples/headers_namespaces/animal.hpp | 22 +++ examples/headers_namespaces/cat.cpp | 17 ++ examples/headers_namespaces/cat.hpp | 16 ++ examples/headers_namespaces/dog.cpp | 10 + examples/headers_namespaces/dog.hpp | 22 +++ examples/headers_namespaces/main.cpp | 38 ++++ .../main_unrelated_namespaces.cpp | 23 +++ .../main_using_directive.cpp | 42 +++++ 16 files changed, 384 insertions(+), 16 deletions(-) create mode 100644 doc/headers_namespaces.adoc create mode 100644 examples/headers_namespaces/CMakeLists.txt create mode 100644 examples/headers_namespaces/animal.hpp create mode 100644 examples/headers_namespaces/cat.cpp create mode 100644 examples/headers_namespaces/cat.hpp create mode 100644 examples/headers_namespaces/dog.cpp create mode 100644 examples/headers_namespaces/dog.hpp create mode 100644 examples/headers_namespaces/main.cpp create mode 100644 examples/headers_namespaces/main_unrelated_namespaces.cpp create mode 100644 examples/headers_namespaces/main_using_directive.cpp diff --git a/doc/BOOST_OPENMETHOD.adoc b/doc/BOOST_OPENMETHOD.adoc index 8394684..cc2cc8d 100644 --- a/doc/BOOST_OPENMETHOD.adoc +++ b/doc/BOOST_OPENMETHOD.adoc @@ -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 `` is included. Changing the value of this symbol has no effect after that point. diff --git a/doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc b/doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc index c3b9eac..f965d93 100644 --- a/doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc +++ b/doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc @@ -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. diff --git a/doc/BOOST_OPENMETHOD_OVERRIDE.adoc b/doc/BOOST_OPENMETHOD_OVERRIDE.adoc index 025c2d4..bf0bb14 100644 --- a/doc/BOOST_OPENMETHOD_OVERRIDE.adoc +++ b/doc/BOOST_OPENMETHOD_OVERRIDE.adoc @@ -57,3 +57,5 @@ auto BOOST_OPENMETHOD_OVERRIDERS(NAME)::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. diff --git a/doc/friendship.adoc b/doc/friendship.adoc index 5be869f..6276209 100644 --- a/doc/friendship.adoc +++ b/doc/friendship.adoc @@ -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] -// ---- diff --git a/doc/headers_namespaces.adoc b/doc/headers_namespaces.adoc new file mode 100644 index 0000000..446fccc --- /dev/null +++ b/doc/headers_namespaces.adoc @@ -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`. 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 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, virtual_ptr), void); + +} // namespace app_specific_behavior + +BOOST_OPENMETHOD_OVERRIDE( + meet, (std::ostream& os, virtual_ptr, virtual_ptr), 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, virtual_ptr), 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, virtual_ptr), void) { + os << "ignore"; +} +``` diff --git a/doc/tutorial.adoc b/doc/tutorial.adoc index b54727c..3be5cd6 100644 --- a/doc/tutorial.adoc +++ b/doc/tutorial.adoc @@ -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[] diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 98a21a2..09624ce 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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) diff --git a/examples/headers_namespaces/CMakeLists.txt b/examples/headers_namespaces/CMakeLists.txt new file mode 100644 index 0000000..8ec1693 --- /dev/null +++ b/examples/headers_namespaces/CMakeLists.txt @@ -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) diff --git a/examples/headers_namespaces/animal.hpp b/examples/headers_namespaces/animal.hpp new file mode 100644 index 0000000..db9bad4 --- /dev/null +++ b/examples/headers_namespaces/animal.hpp @@ -0,0 +1,22 @@ +// animal.hpp + +#ifndef ANIMAL_HPP +#define ANIMAL_HPP + +#include +#include + +namespace animals { + +struct Animal { + Animal(std::string name) : name(name) { + } + std::string name; + virtual ~Animal() = default; +}; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); + +} // namespace animals + +#endif // ANIMAL_HPP diff --git a/examples/headers_namespaces/cat.cpp b/examples/headers_namespaces/cat.cpp new file mode 100644 index 0000000..a223cf0 --- /dev/null +++ b/examples/headers_namespaces/cat.cpp @@ -0,0 +1,17 @@ +// cat.cpp + +#include +#include + +#include "cat.hpp" + +namespace felines { + +BOOST_OPENMETHOD_CLASSES(animals::Animal, Cat); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream & os, virtual_ptr cat), void) { + os << cat->name << " hisses"; +} + +} diff --git a/examples/headers_namespaces/cat.hpp b/examples/headers_namespaces/cat.hpp new file mode 100644 index 0000000..f1e4c57 --- /dev/null +++ b/examples/headers_namespaces/cat.hpp @@ -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 diff --git a/examples/headers_namespaces/dog.cpp b/examples/headers_namespaces/dog.cpp new file mode 100644 index 0000000..cf54eba --- /dev/null +++ b/examples/headers_namespaces/dog.cpp @@ -0,0 +1,10 @@ +#include +#include + +#include "dog.hpp" + +namespace canines { + +BOOST_OPENMETHOD_CLASSES(animals::Animal, Dog); + +} diff --git a/examples/headers_namespaces/dog.hpp b/examples/headers_namespaces/dog.hpp new file mode 100644 index 0000000..63e08f2 --- /dev/null +++ b/examples/headers_namespaces/dog.hpp @@ -0,0 +1,22 @@ +#ifndef CANINES_HPP +#define CANINES_HPP + +#include +#include + +#include "animal.hpp" + +namespace canines { + +struct Dog : animals::Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_INLINE_OVERRIDE( + poke, (std::ostream & os, virtual_ptr dog), void) { + os << dog->name << " barks"; +} + +} // namespace canines + +#endif // CANINES_HPP diff --git a/examples/headers_namespaces/main.cpp b/examples/headers_namespaces/main.cpp new file mode 100644 index 0000000..ad6bcc4 --- /dev/null +++ b/examples/headers_namespaces/main.cpp @@ -0,0 +1,38 @@ +#include +#include +#include + +#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 dog), void) { + next(os, dog); + os << " and bites back"; +} + +auto main() -> int { + boost::openmethod::initialize(); + + std::unique_ptr felix(new felines::Cat("Felix")); + std::unique_ptr snoopy(new canines::Dog("Snoopy")); + std::unique_ptr 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; +} diff --git a/examples/headers_namespaces/main_unrelated_namespaces.cpp b/examples/headers_namespaces/main_unrelated_namespaces.cpp new file mode 100644 index 0000000..eb7522e --- /dev/null +++ b/examples/headers_namespaces/main_unrelated_namespaces.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +#include "animal.hpp" +#include "cat.hpp" +#include "dog.hpp" + +using animals::Animal; + +namespace app_specific_behavior { + +BOOST_OPENMETHOD( + meet, (std::ostream&, virtual_ptr, virtual_ptr), void); + +} + +using app_specific_behavior::BOOST_OPENMETHOD_GUIDE(meet); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (std::ostream& os, virtual_ptr, virtual_ptr), void) { + os << "ignore"; +} diff --git a/examples/headers_namespaces/main_using_directive.cpp b/examples/headers_namespaces/main_using_directive.cpp new file mode 100644 index 0000000..1d7c26e --- /dev/null +++ b/examples/headers_namespaces/main_using_directive.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#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 dog), void) { + next(os, dog); + os << " and bites back"; +} + +auto main() -> int { + boost::openmethod::initialize(); + + std::unique_ptr felix(new Cat("Felix")); + std::unique_ptr snoopy(new Dog("Snoopy")); + std::unique_ptr 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; +}