Skip to content

Commit

Permalink
new take on friendship
Browse files Browse the repository at this point in the history
  • Loading branch information
jll63 committed Mar 22, 2020
1 parent 411c8ca commit 304d4db
Show file tree
Hide file tree
Showing 23 changed files with 675 additions and 143 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/).
126 changes: 116 additions & 10 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -175,22 +175,31 @@ YOMM2_DECLARE(bool, approve, (virtual_<Role&>, virtual_<Expense&>), 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:
```
Expand Down Expand Up @@ -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...);
...
Expand Down
2 changes: 1 addition & 1 deletion dev/run-target
Original file line number Diff line number Diff line change
@@ -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 {} \;
5 changes: 2 additions & 3 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions examples/containers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
111 changes: 111 additions & 0 deletions examples/containers/README.md
Original file line number Diff line number Diff line change
@@ -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).
31 changes: 31 additions & 0 deletions examples/containers/arc_painter.cpp
Original file line number Diff line number Diff line change
@@ -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 <iostream>

#include <yorel/yomm2/cute.hpp>

#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";
}

}
}
Loading

0 comments on commit 304d4db

Please sign in to comment.