From 304d4dbd360718f055296bd224b69d45fada6721 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 22 Feb 2020 11:25:21 -0500 Subject: [PATCH] new take on friendship --- CMakeLists.txt | 2 +- README.md | 3 + REFERENCE.md | 126 ++++++++++++++++-- dev/run-target | 2 +- examples/CMakeLists.txt | 5 +- examples/containers/CMakeLists.txt | 12 ++ examples/containers/README.md | 111 +++++++++++++++ examples/containers/arc_painter.cpp | 31 +++++ .../containers/concrete_shape_painters.cpp | 38 ++++++ examples/containers/geometries.hpp | 40 ++++++ examples/containers/line_painter.cpp | 12 ++ examples/containers/line_painter.hpp | 27 ++++ examples/containers/main.cpp | 34 +++++ examples/containers/painter.cpp | 12 ++ examples/containers/painter.hpp | 48 +++++++ examples/containers/segment_painter.cpp | 33 +++++ examples/containers/shape_painter.cpp | 30 +++++ examples/containers/shape_painter.hpp | 24 ++++ examples/friendship.cpp | 88 ------------ include/yorel/yomm2.hpp | 122 ++++++++++++----- include/yorel/yomm2/cute.hpp | 8 +- src/yomm2.cpp | 2 +- tests/whitebox.cpp | 8 +- 23 files changed, 675 insertions(+), 143 deletions(-) create mode 100644 examples/containers/CMakeLists.txt create mode 100644 examples/containers/README.md create mode 100644 examples/containers/arc_painter.cpp create mode 100644 examples/containers/concrete_shape_painters.cpp create mode 100644 examples/containers/geometries.hpp create mode 100644 examples/containers/line_painter.cpp create mode 100644 examples/containers/line_painter.hpp create mode 100644 examples/containers/main.cpp create mode 100644 examples/containers/painter.cpp create mode 100644 examples/containers/painter.hpp create mode 100644 examples/containers/segment_painter.cpp create mode 100644 examples/containers/shape_painter.cpp create mode 100644 examples/containers/shape_painter.hpp delete mode 100644 examples/friendship.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a31192a3..ba9914d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ if (${YOMM2_ENABLE_TESTS}) add_test (adventure examples/adventure) add_test (next examples/next) add_test (asteroids examples/asteroids) - add_test (friendship examples/friendship) + add_test (containers examples/containers/containers) endif() if (NOT DEFINED(YOMM2_ENABLE_BENCHMARKS)) diff --git a/README.md b/README.md index 24d1af62..187f01eb 100644 --- a/README.md +++ b/README.md @@ -232,4 +232,7 @@ The library comes with a series of examples: * [Adventure: a 3-method example](examples/adventure.cpp) +* [friendship: an example with namespaces, method containers and friend + declarations](examples/friendship) + I presented the library at CppCon 2018. Here are [the video recording](https://www.youtube.com/watch?v=xkxo0lah51s) and [the slides](https://jll63.github.io/yomm2/slides/). diff --git a/REFERENCE.md b/REFERENCE.md index c237cacf..522b0f14 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -76,7 +76,7 @@ with `YOMM2_CLASS`. This means all classes that are marked with `virtual_`, and all their subclasses. Each class registration must list the base classes that may be involved in method calls. -`register_class` is an alias for `YOMM2_CLASS` created by header +`register_class` is an alias for `YOMM2_CLASS` provided by header `yorel/yomm2/cute.hpp`. #### Examples: @@ -135,20 +135,20 @@ a virtual Animal argument in a method declaration. ``` YOMM2_DECLARE(return_type, method, (type...)); -return_type rv = method(unmarked_type... arg); +return_type rv = method(unspecified_type... arg); ``` Declare a method. Create an inline function `method` that returns a `return type` and takes an -argument list of `unmarked_type...`, which consist of `type...` without the +argument list of `unspecified_type...`, which consist of `type...` without the `virtual_` marker. At least one `type` (but not necessarily all) must be marked with `virtual_`. When `method` is called, the dynamic types of the arguments marked with `virtual_` are examined, and the most specific definition compatible with -`unmarked_type...` is called. If no compatible definition exists, or if +`unspecified_type...` is called. If no compatible definition exists, or if several compatible definitions exist but none of them is more specific than all the others, the call is illegal and an error handler is executed. By default it writes a diagnostic on `std::cerr ` and terminates the program via @@ -161,7 +161,7 @@ NOTE: * The parameters in `type...` consist of _just_ a type, e.g. `int` is correct but `int i` is not. -`declare_method` is an alias for `YOMM2_DECLARE` created by header +`declare_method` is an alias for `YOMM2_DECLARE` provided by header `yorel/yomm2/cute.hpp`. ## Examples: @@ -175,22 +175,31 @@ YOMM2_DECLARE(bool, approve, (virtual_, virtual_), double); #### Synopsis: ``` -YOMM2_DEFINE(return_type, name, (unmarked_type... argument)) { +YOMM2_DEFINE(return_type, name, (unspecified_type... argument)) { + .... +} + +YOMM2_DEFINE(container, return_type, name, (unspecified_type... argument)) { .... } ``` Add an implementation to a method. -Locate a method that can be called with the specified `unmarked_type...` list +Locate a method that can be called with the specified `unspecified_type...` list and add the definition to the method's list of definitions. The method must exist and must be unique. `return_type` must be covariant with the method's return type. `return_type` may be `auto`. NOTE that the types of the arguments are _not_ marked with `virtual_`. -`define_method` is an aliases for `YOMM2_DEFINE` and `YOMM2_END` -created by header `yorel/yomm2/cute.hpp`. +If `container` is specified, the method definition is placed inside the said +container, which must have been declared with `YOMM2_DECLARE_METHOD_CONTAINER` +or `method_container`. See the documentation of +`YOMM2_DECLARE_METHOD_CONTAINER` for more information on method containers. + +`define_method` is an alias for `YOMM2_DEFINE`, provided by header +`yorel/yomm2/cute.hpp`. ## Examples: ``` @@ -222,11 +231,108 @@ YOMM2_DEFINE(std::string, meet, (Cat& cat, Dog& dog)) { } ``` +## macros YOMM2_METHOD_INLINE, define_method_inline + +#### Synopsis: +``` +YOMM2_DEFINE_INLINE(container, return_type, name, (unspecified_type... argument)) { + .... +} +``` + +Add an implementation to a method, inside a container, and make it inline. + +Like the `YOMM2_DEFINE(container, ...)` macro, define a method inside the +specified container, which must have been declared with +`YOMM2_DECLARE_METHOD_CONTAINER` or `method_container`. The definition has the +`inline` storage class, and thus can be placed in a header file and is a +potential candidate for inlining. + +See the documentation of `YOMM2_DECLARE_METHOD_CONTAINER` for more information +on method containers. + +`define_method_inline` is an alias for `YOMM2_DEFINE_INLINE`, provided by +header `yorel/yomm2/cute.hpp`. + +## macros YOMM2_DECLARE_METHOD_CONTAINER, method_container + +#### Synopsis: + +``` +YOMM2_DECLARE_METHOD_CONTAINER(container) +YOMM2_DECLARE_METHOD_CONTAINER(container, return_type, name, (unspecified_type... argument)) +``` + +Declare a method container, and optionally a method definition inside that +container. + +Method containers are collections of method definitions that can be addressed +by their name, return type and signature. This makes it possible for a class to +grant friendship to all the methods inside a container, or to a single method +with a specific name, return type and signature - see `YOMM2_FRIEND`. It also +makes it possible to retrieve a specific method, in order to call it or create +a pointer to it - see `YOMM2_DEFINITION`. See [containers](examples/containers) +for an example. + +This macro only creates declarations, and thus can be placed in a header +file. The four argument form makes it possible to access a method definition +across translation units. + +Method containers are implemented as templates, and method definitions scoped +inside containers are implemented as template specializations. Thus methods can +only be added to a container defined in the same namespace, or a namespace +nested inside the namespace where the container has been declared. + +`method_container` is an alias for `YOMM2_DECLARE_METHOD_CONTAINER`, provided +by header `yorel/yomm2/cute.hpp`. + +## macros YOMM2_FRIEND, friend_method + +#### Synopsis: + +``` +YOMM2_FRIEND(container) +YOMM2_FRIEND(container, return_type, (unspecified_type... argument)) +``` + +Grant friendship to all the methods inside a container friend of a class, or to +a specific method. See [containers](examples/containers) for an example. + +`friend_method_container` is an alias for `YOMM2_FRIEND`, provided by header +`yorel/yomm2/cute.hpp`. + +## macros YOMM2_DEFINITION, method_definition + +#### Synopsis: + +``` +YOMM2_DEFINITION(container, return_type, (unspecified_type... argument)) +``` + +Retrieve a method definition with a given return type and signature from a +container. + +The resulting method can be used as a normal function reference. It can be +called, or its address can be taken. In particular, this makes it possible for +a method definition to call a base method as part of its implementation, in the +same manner as an ordinary virtual function can call a specific base function +by prefixing its name with a base class name. + +Note that the preferred way of calling the overriden method is via `next`. In +normal circumstances, a method definition cannot assume which "super" or "base" +function is the best choice, since the set of methods pertaining to the same +declaration is open. + +See [containers](examples/containers) for an example. + +`method_definition` is an alias for `YOMM2_DEFINITION`, provided by header +`yorel/yomm2/cute.hpp`. + ## function next #### Synopsis: ``` -YOMM2_DEFINE(return_type, name, (type... arg)) { +YOMM2_DEFINE(return_type, name, (unspecified_type... arg)) { .... next(arg...); ... diff --git a/dev/run-target b/dev/run-target index 2d99555e..478aa296 100755 --- a/dev/run-target +++ b/dev/run-target @@ -1,4 +1,4 @@ COMPILER=$1 BUILD=$2 TARGET=$3 -cd build/$COMPILER/$BUILD && make $TARGET && find . -name $TARGET -exec $YOMM2_RUN_TARGET_PREFIX {} \; +cd build/$COMPILER/$BUILD && make $TARGET && find . -name $TARGET -type f -exec $YOMM2_RUN_TARGET_PREFIX {} \; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5b16d2db..9e7cad61 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -27,9 +27,8 @@ add_executable(asteroids asteroids.cpp) target_link_libraries (asteroids yomm2) add_test (asteroids asteroids) -add_executable(friendship friendship.cpp) -target_link_libraries (friendship yomm2) -add_test (friendship friendship) +add_subdirectory (containers) +add_test (containers containers) if(NOT MSVC) set(CMAKE_SKIP_BUILD_RPATH TRUE) diff --git a/examples/containers/CMakeLists.txt b/examples/containers/CMakeLists.txt new file mode 100644 index 00000000..501a409f --- /dev/null +++ b/examples/containers/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2018-2020 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( + containers + main.cpp + shape_painter.cpp concrete_shape_painters.cpp + line_painter.cpp arc_painter.cpp segment_painter.cpp + painter.cpp) +target_link_libraries (containers yomm2) diff --git a/examples/containers/README.md b/examples/containers/README.md new file mode 100644 index 00000000..32bb0bd7 --- /dev/null +++ b/examples/containers/README.md @@ -0,0 +1,111 @@ +# Method Containers, Namespaces, Inline and Friendship + +This example shows how to use method containers to grant friendship to method +definitions, and access method defintions across translation units and +namespaces. + +Header [`geometries.hpp`](geometries.hpp) defines a class hierarchy in +namespace `geometries`, representing 1D and 2D geometrical entities. Its +structure is as follows: + +``` +header geometries.hpp + namespace geometries + class Geometry + class Line + class Arc + class Segment + class Shape + class Square + class Circle +``` + +Header [`painter.hpp`](painter.hpp) defines a mechanism for geometry +objects. It consists of a `Painter` class, a `paintObject` open method, two +container declarations inside two nested namespaces, respectively for +1-dimensional and 2-dimensional entities. + +Its structure is as follows: + +``` +painter.hpp + namespace painter + namespace paint1d + method container painters + namespace paint2d + method container painters + class Painter + friend declaration for all methods defined in paint1d::painters + friend declaration for the method defined in paint2d::painters taking a Shape + declaration of open method paintObject +``` + +Note that we are using *two* distinct containers, both called `painters`, but +one is in namespace `paint1d` and the other in namespace `paint2d`. + +[`line_painter.hpp`](line_painter.hpp) defines an *inline* implementation of +`paintObject` for `Line`: + +``` +header line_painter.hpp + namespace painter + namespace paint1d + define paintObject for `Line`, making it inline +``` + +[`segment_painter.hpp`](segment_painter.hpp) implements `paintObject` for +`Segment`, calling the base method for `Line` in the process: + +``` +translation unit segment_painter.cpp + namespace painter + namespace paint1d + define paintObject for `Segment` + call inline `paintObject` for `Line` from container `paint1d::painters` +``` + +[`arc_painter.cpp`](arc_painter.cpp) does the same for `Arc`. + +Note that all three method definitions (for `Line`, `Segment` and `Arc`) can +access the private parts of `Painter`, because they are defined inside a +container that was granted friendship as a whole. + +Also note that the `next` mechanism is not used, instead the method definition for +`Line` is always used. + +[`shape_painter.hpp`](shape_painter.hpp) declares (but does not define) that +container `paint2d::painters` contains a `paintObject` method for `Shape`: + +``` +header shape_painter.hpp + namespace painter + namespace paint2d + declare method container `painters` and inside it, `paintObject(Painter, Shape)` +``` + +[`shape_painter.cpp`](shape_painter.cpp) defines the said method: + +``` +translation unit shape_painter.cpp + namespace painter + namespace paint2d + define `paintObject(Painter, Shape)` inside method container `painters` +``` + +Note that this method definition is a friend of `Painter`, and thus can access +its private parts. + +Finally, [`concrete_shape_painters.cpp`](concrete_shape_painters.cpp) defines +`paintObject` for `Square` and `Circle`: + +``` +translation unit shape_painter.cpp + namespace painter + namespace paint2d + define `paintObject(Painter, Square)` inside method container `painters` + define `paintObject(Painter, Circle)` inside method container `painters` +``` + +Both call base method `paintObject(Painter, Shape)` declared in +[`shape_painter.hpp`](shape_painter.hpp) and defined in +[`shape_painter.cpp`](shape_painter.cpp). diff --git a/examples/containers/arc_painter.cpp b/examples/containers/arc_painter.cpp new file mode 100644 index 00000000..4300a847 --- /dev/null +++ b/examples/containers/arc_painter.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#include + +#include + +#include "geometries.hpp" +#include "line_painter.hpp" + +register_class(geometries::Arc, geometries::Line); + +namespace painter { +namespace paint1d { + +define_method( + painters, + void, paintObject, (Painter& painter, const geometries::Arc& arc)) +{ + ++painter.counter; + method_definition(painters, void, (Painter&, const geometries::Line&))(painter, arc); + std::cout << " " << "painting arc\n"; +} + +} +} diff --git a/examples/containers/concrete_shape_painters.cpp b/examples/containers/concrete_shape_painters.cpp new file mode 100644 index 00000000..acac28c1 --- /dev/null +++ b/examples/containers/concrete_shape_painters.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#include + +#include "geometries.hpp" +#include "shape_painter.hpp" + +register_class(geometries::Shape, geometries::Geometry); +register_class(geometries::Square, geometries::Shape); +register_class(geometries::Circle, geometries::Shape); + +namespace painter { +namespace paint2d { + +define_method( + painters, + void, paintObject, (Painter& painter, const geometries::Square& square)) +{ + method_definition(painters, void, (Painter&, const geometries::Shape&))(painter, square); + std::cout << "painting square\n"; +} + +define_method( + painters, + void, paintObject, (Painter& painter, const geometries::Circle& circle)) +{ + method_definition(painters, void, (Painter&, const geometries::Shape&))(painter, circle); + std::cout << "painting Circle\n"; +} + +} +} diff --git a/examples/containers/geometries.hpp b/examples/containers/geometries.hpp new file mode 100644 index 00000000..2a8b8cb6 --- /dev/null +++ b/examples/containers/geometries.hpp @@ -0,0 +1,40 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#ifndef GEOMETRY_DEFINED +#define GEOMETRY_DEFINED + +namespace geometries { + +class Geometry +{ +public: + virtual ~Geometry() {} +}; + +class Line: public Geometry { +}; + +class Arc : public Line { +}; + +class Segment : public Line { +}; + +class Shape : public Geometry { +}; + +class Square : public Shape { +}; + +class Circle : public Shape { +}; + +} // namespace geometries + +#endif diff --git a/examples/containers/line_painter.cpp b/examples/containers/line_painter.cpp new file mode 100644 index 00000000..e2a37f77 --- /dev/null +++ b/examples/containers/line_painter.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#include "geometries.hpp" +#include "painter.hpp" + +register_class(geometries::Line, geometries::Geometry); diff --git a/examples/containers/line_painter.hpp b/examples/containers/line_painter.hpp new file mode 100644 index 00000000..a292a696 --- /dev/null +++ b/examples/containers/line_painter.hpp @@ -0,0 +1,27 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#ifndef LINE_PAINTER_DEFINED +#define LINE_PAINTER_DEFINED + +#include "painter.hpp" + +namespace painter { +namespace paint1d { + +define_method_inline( + painters, + void, paintObject, (Painter& painter, const geometries::Line& arc)) +{ + std::cout << "#" << painter.counter; +} + +} +} + +#endif diff --git a/examples/containers/main.cpp b/examples/containers/main.cpp new file mode 100644 index 00000000..1ded25e2 --- /dev/null +++ b/examples/containers/main.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#include + +#include +#include +#include + +#include "geometries.hpp" +#include "painter.hpp" + +using yorel::yomm2::virtual_; +using std::cout; + +int main() { + yorel::yomm2::update_methods(); + + const geometries::Geometry& arc = geometries::Arc(); + const geometries::Geometry& segment = geometries::Segment(); + const geometries::Geometry& square = geometries::Square(); + const geometries::Geometry& circle = geometries::Circle(); + + painter::Painter painter; + painter.paint(arc); + painter.paint(segment); + painter.paint(square); + painter.paint(circle); +} diff --git a/examples/containers/painter.cpp b/examples/containers/painter.cpp new file mode 100644 index 00000000..0b7843cc --- /dev/null +++ b/examples/containers/painter.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#include "geometries.hpp" +#include "painter.hpp" + +register_class(geometries::Geometry); diff --git a/examples/containers/painter.hpp b/examples/containers/painter.hpp new file mode 100644 index 00000000..e826b582 --- /dev/null +++ b/examples/containers/painter.hpp @@ -0,0 +1,48 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#ifndef PAINTER_DEFINED +#define PAINTER_DEFINED + +#include "geometries.hpp" + +#include + +namespace painter { + +namespace paint1d { method_container(painters); } +namespace paint2d { method_container(painters); } + +class Painter +{ +public: + void paint(const geometries::Geometry& geometry); + int painted() const; +private: + int counter = 0; + friend_method(paint1d::painters); + friend_method(paint2d::painters, void, (Painter&, const geometries::Shape&)); +}; + +// Implements paint +declare_method( + void, + paintObject, + (Painter&, yorel::yomm2::virtual_)); + +inline void Painter::paint(const geometries::Geometry& geometry) { + paintObject(*this, geometry); +} + +inline int Painter::painted() const { + return counter; +} + +} // namespace painter + +#endif diff --git a/examples/containers/segment_painter.cpp b/examples/containers/segment_painter.cpp new file mode 100644 index 00000000..77f59841 --- /dev/null +++ b/examples/containers/segment_painter.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#include + +#include + +#include "geometries.hpp" +#include "line_painter.hpp" + +register_class(geometries::Segment, geometries::Line); + +namespace painter { +namespace paint1d { + +define_method( + painters, + void, paintObject, (Painter& painter, const geometries::Segment& segment)) +{ + ++painter.counter; + method_definition( + painters, void, (Painter&, const geometries::Line&))( + painter, segment); + std::cout << " " << "painting segment\n"; +} + +} +} diff --git a/examples/containers/shape_painter.cpp b/examples/containers/shape_painter.cpp new file mode 100644 index 00000000..ccc2ea3a --- /dev/null +++ b/examples/containers/shape_painter.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#include + +#include "geometries.hpp" +#include "painter.hpp" + +register_class(geometries::Shape, geometries::Geometry); + +namespace painter { +namespace paint2d { + +define_method( + painters, + void, paintObject, (Painter& painter, const geometries::Shape& shape)) +{ + ++painter.counter; + static int counter; + ++counter; + std::cout << "#" << painter.counter << " #" << counter << " "; +} + +} +} diff --git a/examples/containers/shape_painter.hpp b/examples/containers/shape_painter.hpp new file mode 100644 index 00000000..eeebb7f8 --- /dev/null +++ b/examples/containers/shape_painter.hpp @@ -0,0 +1,24 @@ +// Copyright (c) 2020 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) + +// This exmaple is based on sample code provided by Github user matpen in +// https://github.com/jll63/yomm2/issues/7 + +#ifndef SHAPE_PAINTER_DEFINED +#define SHAPE_PAINTER_DEFINED + +#include "painter.hpp" + +namespace painter { +namespace paint2d { + +method_container( + painters, + void, paintObject, (Painter& painter, const geometries::Shape& shape)); + +} +} + +#endif diff --git a/examples/friendship.cpp b/examples/friendship.cpp deleted file mode 100644 index 3250e67c..00000000 --- a/examples/friendship.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2020 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) - -// This is based on sample code provided by Github user matpen in -// https://github.com/jll63/yomm2/issues/7 - -#include - -#include -#include -#include - -using yorel::yomm2::virtual_; -using std::cout; - -class Geometry -{ -public: - virtual ~Geometry() {} -}; - -class Arc : public Geometry -{ -public: - virtual ~Arc() {} -}; - -class Edge : public Geometry -{ -public: - virtual ~Edge() {} -}; - -register_class(Geometry); -register_class(Arc, Geometry); -register_class(Edge, Geometry); - -class Painter -{ -public: - void paint(const Geometry& geometry); -private: - int counter = 0; - friend_named_method(paint_geo); -}; - -// Implements paint -declare_method(void, paintObject, (Painter&, virtual_)); - -// Catch-all paint(Geometry) -define_named_method( - paint_geo, - void, paintObject, (Painter& painter, const Geometry& /*geometry*/)) { - std::cout << "painting geometry (" << painter.counter << ")\n"; - ++painter.counter; -} - -// Specific paint(Arc) -define_method(void, paintObject, (Painter& painter, const Arc& arc)) { - //next(painter, arc); - // also provides a way of calling a specific override: - call_named_method(paint_geo, (painter, arc)); - std::cout << " painting arc\n"; -} - -// Specific paint(Edge) -define_method(void, paintObject, (Painter& painter, const Edge& edge)) { - next(painter, edge); - std::cout << " painting edge\n"; -} - -void Painter::paint(const Geometry& geometry) -{ - paintObject(*this, geometry); -} - -int main() { - yorel::yomm2::update_methods(); - - Arc arc; - Edge edge; - Painter painter; - - painter.paint(arc); - painter.paint(edge); -} diff --git a/include/yorel/yomm2.hpp b/include/yorel/yomm2.hpp index e142c1c9..22dd7c35 100644 --- a/include/yorel/yomm2.hpp +++ b/include/yorel/yomm2.hpp @@ -110,20 +110,32 @@ yOMM2_ALIST, ARGS)); \ } -#define YOMM2_DEFINE(RETURN_T, ID, ARGS) \ +#if !BOOST_PP_VARIADICS_MSVC +#define YOMM2_DEFINE(...) \ + BOOST_PP_OVERLOAD(YOMM2_DEFINE_, __VA_ARGS__)(__VA_ARGS__) +#else +#define YOMM2_DEFINE(...) \ + BOOST_PP_CAT(BOOST_PP_OVERLOAD(YOMM2_DEFINE_, __VA_ARGS__) \ + (__VA_ARGS__), BOOST_PP_EMPTY()) +#endif + +#define YOMM2_DEFINE_3(RETURN_T, ID, ARGS) \ yOMM2_DEFINE(yOMM2_GENSYM, RETURN_T, ID, ARGS) -#define yOMM2_DEFINE(NS, RETURN_T, ID, ARGS) \ - namespace { \ - namespace NS { \ +#define yOMM2_SELECT_METHOD(RETURN_T, ID, ARGS) \ template struct _yOMM2_select; \ template struct _yOMM2_select { \ using type = decltype(ID(::yorel::yomm2::detail::discriminator(), \ std::declval()...)); \ }; \ using _yOMM2_method = _yOMM2_select::type; \ - using _yOMM2_return_t = _yOMM2_method::return_type; \ - _yOMM2_return_t (*next) ARGS; \ + using _yOMM2_return_t = _yOMM2_method::return_type + +#define yOMM2_DEFINE(NS, RETURN_T, ID, ARGS) \ + namespace { \ + namespace NS { \ + yOMM2_SELECT_METHOD(RETURN_T, ID, ARGS); \ + _yOMM2_method::next_type next; \ struct _yOMM2_spec { static RETURN_T yOMM2_body ARGS; }; \ ::yorel::yomm2::detail:: \ register_spec<_yOMM2_return_t, _yOMM2_method, _yOMM2_spec, void ARGS> \ @@ -132,37 +144,68 @@ } } \ RETURN_T NS::_yOMM2_spec::yOMM2_body ARGS -#define YOMM2_DECLARE_NAMED_METHOD(CONTAINER) struct CONTAINER -#define YOMM2_FRIEND_NAMED_METHOD(CONTAINER) friend struct CONTAINER +#if !BOOST_PP_VARIADICS_MSVC +#define YOMM2_DECLARE_METHOD_CONTAINER(...) \ + BOOST_PP_OVERLOAD(YOMM2_DECLARE_METHOD_CONTAINER_, __VA_ARGS__)(__VA_ARGS__) +#else +#define YOMM2_DECLARE_METHOD_CONTAINER(...) \ + BOOST_PP_CAT(BOOST_PP_OVERLOAD(YOMM2_DECLARE_METHOD_CONTAINER_, __VA_ARGS__) \ + (__VA_ARGS__), BOOST_PP_EMPTY()) +#endif -#define YOMM2_NAMED_DEFINE(CONTAINER, RETURN_T, ID, ARGS) \ - yOMM2_NAMED_DEFINE(yOMM2_GENSYM, CONTAINER, RETURN_T, ID, ARGS) +#define YOMM2_DECLARE_METHOD_CONTAINER_1(CONTAINER) \ + template struct CONTAINER -#define YOMM2_CALL_NAMED_METHOD(CONTAINER, ARGS) CONTAINER::yOMM2_body ARGS +#define YOMM2_DECLARE_METHOD_CONTAINER_4(CONTAINER, RETURN_T, ID, ARGS) \ + template struct CONTAINER; \ + YOMM2_DECLARE_METHOD_CONTAINER_4_NS(yOMM2_GENSYM, CONTAINER, RETURN_T, ID, ARGS) \ -#define yOMM2_NAMED_DEFINE(NS, CONTAINER, RETURN_T, ID, ARGS) \ +#define YOMM2_DECLARE_METHOD_CONTAINER_4_NS(NS, CONTAINER, RETURN_T, ID, ARGS) \ + template struct CONTAINER; \ namespace { namespace NS { \ - template struct _yOMM2_select; \ - template struct _yOMM2_select { \ - using type = decltype(ID(::yorel::yomm2::detail::discriminator(), \ - std::declval()...)); \ - }; \ - using _yOMM2_method = _yOMM2_select::type; \ - using _yOMM2_return_t = _yOMM2_method::return_type; \ + yOMM2_SELECT_METHOD(RETURN_T, ID, ARGS); \ } } \ - struct CONTAINER { \ - static NS::_yOMM2_return_t (*next) ARGS; \ + template<> struct CONTAINER { \ + static NS::_yOMM2_method::next_type next; \ static RETURN_T yOMM2_body ARGS; \ - }; \ - NS::_yOMM2_return_t (*CONTAINER::next) ARGS; \ + } + +#define YOMM2_DEFINE_4(CONTAINER, RETURN_T, ID, ARGS) \ + yOMM2_DEFINE_METHOD_IN(yOMM2_GENSYM, , CONTAINER, RETURN_T, ID, ARGS) + +#define YOMM2_DEFINE_INLINE(CONTAINER, RETURN_T, ID, ARGS) \ + yOMM2_DEFINE_METHOD_IN(yOMM2_GENSYM, inline, CONTAINER, RETURN_T, ID, ARGS) + +#define yOMM2_DEFINE_METHOD_IN(NS, INLINE, CONTAINER, RETURN_T, ID, ARGS) \ + YOMM2_DECLARE_METHOD_CONTAINER_4_NS(NS, CONTAINER, RETURN_T, ID, ARGS); \ + INLINE NS::_yOMM2_method::next_type CONTAINER::next; \ namespace { namespace NS { \ - ::yorel::yomm2::detail:: \ - register_spec<_yOMM2_return_t, _yOMM2_method, CONTAINER, void ARGS> \ + INLINE ::yorel::yomm2::detail:: \ + register_spec<_yOMM2_return_t, _yOMM2_method, CONTAINER, void ARGS> \ _yOMM2_init( \ - (void**)&CONTAINER::next, YOMM2_TRACE_ELSE(#ARGS, typeid(CONTAINER).name())); \ + (void**)&CONTAINER::next, \ + YOMM2_TRACE_ELSE(#ARGS, typeid(CONTAINER).name())); \ } } \ - RETURN_T CONTAINER::yOMM2_body ARGS + INLINE RETURN_T CONTAINER::yOMM2_body ARGS + +#if !BOOST_PP_VARIADICS_MSVC +#define YOMM2_FRIEND(...) \ + BOOST_PP_OVERLOAD(YOMM2_FRIEND_, __VA_ARGS__)(__VA_ARGS__) +#else +#define YOMM2_FRIEND(...) \ + BOOST_PP_CAT(BOOST_PP_OVERLOAD(YOMM2_FRIEND_, __VA_ARGS__) \ + (__VA_ARGS__), BOOST_PP_EMPTY()) +#endif + +#define YOMM2_FRIEND_1(CONTAINER) \ + template friend struct CONTAINER + +#define YOMM2_FRIEND_3(CONTAINER, RETURN_T, ARGS) \ + friend struct CONTAINER + +#define YOMM2_DEFINITION(CONTAINER, RETURN_T, ARGS) \ + CONTAINER::yOMM2_body #define YOMM2_CLASS(...) \ yOMM2_CLASS2(yOMM2_GENSYM, \ @@ -199,7 +242,8 @@ struct method_call_error { using method_call_error_handler = void (*)(const method_call_error& error); -method_call_error_handler set_method_call_error_handler(method_call_error_handler handler); +method_call_error_handler +set_method_call_error_handler(method_call_error_handler handler); struct default_registry; @@ -215,6 +259,17 @@ struct default_policy : policy { namespace detail { +template +struct next_ptr_t; + +template +struct next_ptr_t { + using type = R(*)(T...); +}; + +template +inline typename next_ptr_t::type next; + extern method_call_error_handler call_error_handler; struct method_info; @@ -831,8 +886,12 @@ template< typename... BASE_PARAM, typename... SPEC_PARAM > -struct wrapper { - static BASE_RETURN body(virtual_arg_t... arg) { +struct wrapper< + BASE_RETURN, + FUNCTION, + BASE_RETURN(BASE_PARAM...), + BASE_RETURN(SPEC_PARAM...)> { + static BASE_RETURN call(virtual_arg_t... arg) { return FUNCTION::yOMM2_body( virtual_traits::template cast( std::forward>(arg), @@ -859,7 +918,7 @@ struct register_spec log() << METHOD::info().name << ": add spec " << name << "\n"); si.pf = (void*) wrapper< RETURN_T, SPEC, typename METHOD::signature_type, RETURN_T(SPEC_ARGS...) - >::body; + >::call; METHOD::for_each_vp_t::template for_spec::collect_class_info(si.vp); METHOD::info().specs.push_back(&si); si.next = next; @@ -893,6 +952,7 @@ struct method { using signature_type = R(A...); using return_type = R; + using next_type = R (*)(virtual_arg_t...); using REG = typename POLICY::registry; using for_each_vp_t = for_each_vp; diff --git a/include/yorel/yomm2/cute.hpp b/include/yorel/yomm2/cute.hpp index aa88c031..29795dc2 100644 --- a/include/yorel/yomm2/cute.hpp +++ b/include/yorel/yomm2/cute.hpp @@ -12,10 +12,10 @@ #define declare_method YOMM2_DECLARE #define define_method YOMM2_DEFINE +#define define_method_inline YOMM2_DEFINE_INLINE -#define declare_named_method YOMM2_DECLARE_NAMED_METHOD -#define define_named_method YOMM2_NAMED_DEFINE -#define call_named_method YOMM2_CALL_NAMED_METHOD -#define friend_named_method YOMM2_FRIEND_NAMED_METHOD +#define method_container YOMM2_DECLARE_METHOD_CONTAINER +#define friend_method YOMM2_FRIEND +#define method_definition YOMM2_DEFINITION #endif diff --git a/src/yomm2.cpp b/src/yomm2.cpp index 840f4f0e..03816f13 100644 --- a/src/yomm2.cpp +++ b/src/yomm2.cpp @@ -25,7 +25,7 @@ namespace detail { #if YOMM2_ENABLE_TRACE struct indent { - indent(int n) : n(n) { + explicit indent(int n) : n(n) { assert(n >= 0); } int n; diff --git a/tests/whitebox.cpp b/tests/whitebox.cpp index bf7cf11d..59ba9293 100644 --- a/tests/whitebox.cpp +++ b/tests/whitebox.cpp @@ -153,22 +153,22 @@ BOOST_AUTO_TEST_CASE(casts) { BOOST_TEST( (wrapper), voidp(const Mammal&)>:: - body(animal)) == &mammal); + call(animal)) == &mammal); BOOST_TEST( (wrapper), voidp(const Carnivore&)>:: - body(animal)) == &carnivore); + call(animal)) == &carnivore); BOOST_TEST( (wrapper), voidp(const Carnivore&)>:: - body(animal)) == &carnivore); + call(animal)) == &carnivore); BOOST_TEST( (wrapper), voidp(const Dog&)>:: - body(animal)) == &dog); + call(animal)) == &dog); }