Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for explicit this #52

Merged
merged 12 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/workflows/gha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ jobs:
- uses: actions/checkout@v4
- name: Build
run: bash tools/gha_osx_clang_14.sh
windows_vs2022:
osx_clang_18:
runs-on: macos-11
steps:
- uses: actions/checkout@v4
- name: Build
run: bash tools/gha_osx_clang_18.sh
windows_vs2022_cpp20:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
Expand All @@ -66,6 +72,27 @@ jobs:
cmake ../ -G "Visual Studio 17 2022" -A x64 -DTANUKI_BUILD_TESTS=yes -DTANUKI_BUILD_TUTORIALS=yes -DTANUKI_WITH_BOOST_S11N=yes
cmake --build . --config Release -j4
ctest -j4 -V -C Release
windows_vs2022_cpp23:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2
- uses: conda-incubator/setup-miniconda@v3
with:
auto-update-conda: true
python-version: "3.10"
channels: conda-forge
channel-priority: strict
- name: Build
shell: pwsh
run: |
conda install -y cmake boost-cpp
mkdir build
cd build
cmake ../ -G "Visual Studio 17 2022" -A x64 -DTANUKI_BUILD_TESTS=yes -DTANUKI_BUILD_TUTORIALS=yes -DTANUKI_WITH_BOOST_S11N=yes -DCMAKE_CXX_STANDARD=23
cmake --build . --config Release -j4
ctest -j4 -V -C Release
conda_coverage:
runs-on: ubuntu-latest
steps:
Expand Down
15 changes: 12 additions & 3 deletions doc/config.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.. _config_settings:

.. cpp:namespace-push:: tanuki

Configuration options
=====================

.. cpp:struct:: template <typename DefaultValueType = void, typename RefIFace = no_ref_iface> requires std::same_as<DefaultValueType, void> || valid_value_type<DefaultValueType> config
.. cpp:struct:: template <typename DefaultValueType = void, typename RefIFace = no_ref_iface> requires (std::same_as<DefaultValueType, void> || valid_value_type<DefaultValueType>) && valid_ref_iface<RefIFace> config

Configuration struct.

Expand Down Expand Up @@ -88,14 +90,21 @@ Configuration options

.. cpp:var:: template <typename T, typename IFace> requires iface_with_impl<IFace, T> inline constexpr std::size_t holder_size

Helper to compute the amount of memory (in bytes) needed to store in a :cpp:class:`wrap`
Helper to compute the total amount of memory (in bytes) needed to statically store in a :cpp:class:`wrap`
a value of type :cpp:type:`T` wrapped by the interface :cpp:type:`IFace`.

.. cpp:var:: template <typename T, typename IFace> requires iface_with_impl<IFace, T> inline constexpr std::size_t holder_align

Helper to compute the amount of memory (in bytes) needed to store in a :cpp:class:`wrap`
Helper to compute the alignment (in bytes) required to statically store in a :cpp:class:`wrap`
a value of type :cpp:type:`T` wrapped by the interface :cpp:type:`IFace`.

.. cpp:concept:: template <typename RefIFace> valid_ref_iface

This concept is satisfied if :cpp:type:`RefIFace` is a valid :ref:`reference interface <ref_interface>`.

When using C++23, this concept is satisfied by any non-cv qualified class type. In C++20, :cpp:type:`RefIFace`
must also define in its scope an ``impl`` class template/template alias depending exactly on one parameter.

.. cpp:concept:: template <auto Cfg> valid_config

Concept for checking that :cpp:var:`Cfg` is a valid instance of :cpp:class:`config`.
Expand Down
2 changes: 1 addition & 1 deletion doc/custom_storage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ storage available for the type-erased value will be less than 24 bytes. How much
is dependent on a variety of factors and difficult to compute in advance.

For this reason, tanuki provides a :cpp:var:`holder_size` helper which can be used to compute
how much **total** storage is needed to store a value of a type ``T`` in a :cpp:class:`wrap`.
how much **total** memory is needed to statically store a value of a type ``T`` in a :cpp:class:`wrap`.
Let us a see a simple example.

We have our usual super-basic interface and its implementation:
Expand Down
90 changes: 76 additions & 14 deletions doc/ref_interface.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,16 @@ neither of which are currently available.

The better news is that tanuki provides a mechanism to deactivate the pointer interface (i.e., operator ``->``)
and activate a *reference interface* instead (i.e., dot-style access to the member functions). Implementing
a reference interface will require you to pick your poison: either accept some
`repetition <https://en.wikipedia.org/wiki/Don%27t_repeat_yourself>`__ or use a macro-based solution.
a reference interface will require a certain amount of
`repetition <https://en.wikipedia.org/wiki/Don%27t_repeat_yourself>`__ that can be partially alleviated
via the use of macros.

Implementing a reference interface
----------------------------------
The following sections explain how to implement reference interfaces. Two APIs can be used: the first one
is always available, the second one is a cleaner and less verbose alternative that however requires
C++23.

Reference interfaces in C++20
-----------------------------

Consider the simple interface from the :ref:`previous tutorial <simple_interface>`, its implementation
and a ``foo_model`` class:
Expand Down Expand Up @@ -86,7 +91,7 @@ an equivalent reference interface which does **not** use the :c:macro:`TANUKI_RE

Here is what is going on: tanuki makes the :cpp:class:`wrap` class inherit from ``foo_ref_iface2::impl``,
so that, via the `curiously recurring template pattern (CRTP) <https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`__,
we can invoke the :cpp:func:`wrap::iface_ptr` function on the :cpp:class:`wrap` object
we can invoke the :cpp:func:`~wrap::iface_ptr()` function on the :cpp:class:`wrap` object
to access a pointer to the ``foo_iface`` interface, via which we finally invoke the ``foo()``
member function. Phew!

Expand All @@ -100,7 +105,7 @@ stomach macros.
After the definition of the reference interfaces, we need to configure the :cpp:class:`wrap` class
to make use of them. This is accomplished by defining custom :cpp:class:`config` instances and using
them in the :cpp:class:`wrap` class. For instance, for the macro-based reference interface we
first write:
first define a custom configuration called ``foo_config1``:

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
Expand All @@ -109,21 +114,21 @@ first write:
The :cpp:class:`config` class is templated over two types. Ignoring the first one for the time
being (its meaning will be explained :ref:`later <def_ctor>`), the second parameter is the reference interface, which
we set to ``foo_ref_iface1`` to select the macro-based reference interface. We also switch off
the pointer interface in :cpp:class:`wrap` via the ``.pointer_interface = false``
`designated initializer <https://en.cppreference.com/w/cpp/language/aggregate_initialization>`__ -- this
will ensure that access to the interface functions via the arrow operator is disabled.
the pointer interface in :cpp:class:`wrap` by setting to ``false`` the :cpp:var:`~config::pointer_interface`
:ref:`configuration setting <config_settings>` -- this will ensure that access to the interface
functions via the arrow operator is disabled.

We can now use the custom configuration instance in the definition of the wrap class:

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
:lines: 49
:lines: 69

And we can confirm that indeed we can now invoke the ``foo()`` member function via the dot operator:

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
:lines: 51-52
:lines: 71-72

.. code-block:: console

Expand All @@ -140,16 +145,73 @@ Second, we can use the custom configuration instance to define another wrap type

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
:lines: 54-57
:lines: 74-77

.. code-block:: console

foo_iface_impl calling foo()

Reference interfaces in C++23
-----------------------------

An alternative API for the definition of reference interfaces is available if your compiler
supports C++23 (more specifically, the so-called
"`deducing this <https://en.cppreference.com/w/cpp/language/member_functions#Explicit_object_member_functions>`__"
feature). With the alternative API, it is not necessary any more to define a nested ``impl``
template, and we can define the member function wrappers directly in the body of the
class instead, like this:

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
:lines: 49-51

Note that, in the C++23 API, we must use the :c:macro:`TANUKI_REF_IFACE_MEMFUN2` macro, rather
than :c:macro:`TANUKI_REF_IFACE_MEMFUN`.

If you cannot or do not want to use the :c:macro:`TANUKI_REF_IFACE_MEMFUN2` macro, here is the implementation
of a C++23 reference interface without macros:

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
:lines: 53-59

In other words, the "deducing this" C++23 feature allows us to avoid having to perform the CRTP cast
manually, and we can invoke the :cpp:func:`~wrap::iface_ptr()` function directly on the ``self``/``this`` argument
instead.

We can then proceed as usual with the definition of the custom configurations using the
new reference interfaces:

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
:lines: 61-63

The definition and usage of the :cpp:class:`wrap` instances is identical to the C++20 API:

.. literalinclude:: ../tutorial/reference_interface.cpp
:language: c++
:lines: 81-89

.. code-block:: console

foo_iface_impl calling foo()
foo_iface_impl calling foo()

As a final comment, it should be noted that the reference interface is useful beyond just enabling dot-style
access to the member functions. For instance, it also allows to define nested types and inline friend functions and operators
Note that the C++20 reference interface API is still available when using C++23 -- tanuki will detect whether
or not the nested template ``impl`` is present and will auto-select the API accordingly.

Tips & tricks
-------------

It should be noted that reference interfaces are useful beyond just enabling dot-style
access to the member functions. For instance, they also allow to define nested types and inline friend functions and operators
that will be accessible via `ADL <https://en.cppreference.com/w/cpp/language/adl>`__.

On the other hand, it is also possible to design an API around the :cpp:class:`wrap` class which does not
employ member functions and which thus needs neither a pointer nor a reference interface. In this usage
mode, the member functions defined in a type-erased interface are used as an implementation detail for free functions,
rather than being invoked directly.

Full code listing
-----------------

Expand Down
12 changes: 10 additions & 2 deletions doc/wrap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,23 @@ The ``wrap`` class

:return: the validity status for *w*.

.. cpp:function:: [[nodiscard]] friend const IFace *iface_ptr(const wrap &w) noexcept

.. cpp:function:: [[nodiscard]] friend const IFace *iface_ptr(const wrap &&w) noexcept

.. cpp:function:: [[nodiscard]] friend IFace *iface_ptr(wrap &w) noexcept

.. cpp:function:: [[nodiscard]] friend IFace *iface_ptr(wrap &&w) noexcept

.. cpp:function:: template <typename T, typename... Args> friend void emplace(wrap &w, Args &&...args)

Emplace a value into a :cpp:class:`wrap`.

This function will first destroy the value in *w* (if *w* is not already in the :ref:`invalid state <invalid_state>`).
It will then construct in *w* a value of type :cpp:type:`T` using the construction arguments :cpp:type:`Args`.

This function is enabled only if :cpp:type:`T` is not :cpp:class:`wrap` and if an instance of :cpp:type:`T`
can be constructed from :cpp:type:`Args`.
This function is enabled only if :cpp:type:`T` is not :cpp:class:`wrap` (that is, it is not possible to emplace
a :cpp:class:`wrap` into itself) and if an instance of :cpp:type:`T` can be constructed from :cpp:type:`Args`.

This function is ``noexcept`` if all these conditions are satisfied:

Expand Down
Loading