From 12013ebc4c12bc7f77b3a84307557e014c3604bf Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Thu, 16 May 2024 08:52:45 +0200 Subject: [PATCH 01/15] Some WIP on the docs, with improvements to concept checking in the implementation. --- doc/wrap.rst | 50 +++++++++++++++++++++++++++++++++++---- include/tanuki/tanuki.hpp | 16 ++++++++----- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/doc/wrap.rst b/doc/wrap.rst index d93cdc6..d4c8b59 100644 --- a/doc/wrap.rst +++ b/doc/wrap.rst @@ -25,9 +25,9 @@ The ``wrap`` class :throws: any exception thrown by the default constructor of the interface implementation or of the :ref:`reference interface `, by the value-initialisation of a non-``void`` :cpp:type:`~config::DefaultValueType` or by memory allocation errors - if the non-``void`` :cpp:type:`~config::DefaultValueType` does not fit in static storage. If it can be - determined at compile time that none of these conditions can occurr, then this constructor - is marked ``noexcept``. + if the non-``void`` :cpp:type:`~config::DefaultValueType` does not fit in static storage + or if reference semantics is being used. If it can be determined at compile time that none + of these conditions can occurr, then this constructor is marked ``noexcept``. .. cpp:function:: explicit wrap(invalid_wrap_t) @@ -73,7 +73,49 @@ The ``wrap`` class :throws: any exception thrown by the default constructor of the interface implementation or of the :ref:`reference interface `, by the construction of the value type or by memory allocation errors - if the value type does not fit in static storage. If it can be + if the value type does not fit in static storage or if reference semantics is being used. If it can be + determined at compile time that none of these conditions can occurr, then this constructor + is marked ``noexcept``. + + .. cpp:function:: template explicit wrap(std::in_place_type_t, U &&...args) + + Generic in-place constructor. + + This constructor will create a :cpp:class:`wrap` containing a type-erased value of type :cpp:type:`T` + constructed from the input argument(s) :cpp:type:`U`. If no input arguments are provided, the internal + value will be value-initialised. + + This constructor is enabled only if *all* the following conditions are satisfied: + + - :cpp:type:`T` is an object type without cv qualifications. + - the :ref:`reference interface ` is default-initialisable; + - the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` + (see the :cpp:concept:`iface_with_impl` concept); + - *args* can be perfectly-forwarded to construct an instance of the value type :cpp:type:`T`. + + :throws: any exception thrown by the default constructor of the interface implementation or of + the :ref:`reference interface `, by the construction of the value type or by memory allocation errors + if the value type does not fit in static storage or if reference semantics is being used. If it can be + determined at compile time that none of these conditions can occurr, then this constructor + is marked ``noexcept``. + + .. cpp:function:: wrap(const wrap &other) + + Copy constructor. + + When employing value semantics, the copy constructor will copy-construct the type-erased value from *other*. + Otherwise, a :cpp:class:`wrap` sharing ownership of the type-erased value with *other* will be constructed. + + This constructor is enabled only if *all* the following conditions are satisfied: + + - the :ref:`reference interface ` is default-initialisable; + - when employing value semantics, the :cpp:var:`~config::copyable` option in :cpp:var:`Cfg` + is activated. + + :throws std\:\:invalid_argument: if the type-erased value is not copy-constructible and value semantics is being used. + :throws: any exception thrown by the default constructor of the interface implementation or of + the :ref:`reference interface `, by the copy-construction of the value type or by memory allocation errors + if the value type does not fit in static storage or if value semantics is being used. If it can be determined at compile time that none of these conditions can occurr, then this constructor is marked ``noexcept``. diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 2e50870..4c31509 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -1423,16 +1423,20 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage - requires std::default_initializable && - // We must be able to construct the holder. - detail::holder_constructible_from - explicit wrap(std::in_place_type_t, U &&...args) noexcept(noexcept(this->ctor_impl(std::forward(args)...)) - && detail::nothrow_default_initializable) + requires + // T must be an a non-cv-qualified object. + std::is_object_v && std::same_as> && std::default_initializable && + // We must be able to construct the holder. + detail::holder_constructible_from + explicit wrap(std::in_place_type_t, + U &&...args) noexcept(noexcept(this->ctor_impl(std::forward(args)...)) + && detail::nothrow_default_initializable) { ctor_impl(std::forward(args)...); } - wrap(const wrap &other) noexcept(Cfg.semantics == wrap_semantics::reference) + wrap(const wrap &other) noexcept(Cfg.semantics == wrap_semantics::reference + && detail::nothrow_default_initializable) requires(Cfg.copyable || Cfg.semantics == wrap_semantics::reference) && std::default_initializable { if constexpr (Cfg.semantics == wrap_semantics::value) { From 51957accf194311c6cebf1144f20c8345a6ff967 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Thu, 16 May 2024 09:57:27 +0200 Subject: [PATCH 02/15] More docs work. --- doc/simple_interface.rst | 4 +-- doc/wrap.rst | 52 ++++++++++++++++++++++----------------- include/tanuki/tanuki.hpp | 12 +++++++-- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/doc/simple_interface.rst b/doc/simple_interface.rst index 49daa8e..7f12126 100644 --- a/doc/simple_interface.rst +++ b/doc/simple_interface.rst @@ -163,7 +163,7 @@ What happens now if we try to construct a ``foo3_wrap`` from an ``int``? The err That is, the constructor of the :cpp:class:`wrap` class now detects that ``int`` does not have an interface implementation, and as a consequence the compiler detects an error *before* trying to invoke the (non-existing) ``foo()`` member function on the ``int``. We can confirm -that the non-constructability of ``foo3_wrap`` from ``int`` is detected at compile time +that the non-constructibility of ``foo3_wrap`` from ``int`` is detected at compile time by checking the ``std::is_constructible`` type trait: .. literalinclude:: ../tutorial/simple_interface.cpp @@ -219,7 +219,7 @@ It works! Bu what happens if we try to construct a ``foo4_wrap`` from an object ``fooable`` nor an ``int``? The :cpp:class:`wrap` class will detect that the interface implementation corresponding to an object of such type is empty (i.e., invalid), and it will thus disable the constructor. We can confirm that this is the case by checking -the constructability of ``foo4_wrap`` from a ``float`` (which is neither +the constructibility of ``foo4_wrap`` from a ``float`` (which is neither ``fooable`` nor an ``int``): .. literalinclude:: ../tutorial/simple_interface.cpp diff --git a/doc/wrap.rst b/doc/wrap.rst index d4c8b59..5544ddc 100644 --- a/doc/wrap.rst +++ b/doc/wrap.rst @@ -44,7 +44,7 @@ The ``wrap`` class Generic constructor. - This constructor will create a :cpp:class:`wrap` from the input value *x*. + This constructor will create a :cpp:class:`wrap` from the input value :cpp:var:`x`. This constructor is enabled only if *all* the following conditions are satisfied: @@ -60,7 +60,7 @@ The ``wrap`` class - the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` (see the :cpp:concept:`iface_with_impl` concept); - - *x* can be perfectly-forwarded to construct an instance of the value type. + - :cpp:var:`x` can be perfectly-forwarded to construct an instance of the value type. This constructor is marked ``explicit`` if either: @@ -71,6 +71,8 @@ The ``wrap`` class Otherwise, the constructor is implicit. + :param x: the input value. + :throws: any exception thrown by the default constructor of the interface implementation or of the :ref:`reference interface `, by the construction of the value type or by memory allocation errors if the value type does not fit in static storage or if reference semantics is being used. If it can be @@ -82,7 +84,7 @@ The ``wrap`` class Generic in-place constructor. This constructor will create a :cpp:class:`wrap` containing a type-erased value of type :cpp:type:`T` - constructed from the input argument(s) :cpp:type:`U`. If no input arguments are provided, the internal + constructed from the input argument(s) :cpp:var:`args`. If no input arguments are provided, the internal value will be value-initialised. This constructor is enabled only if *all* the following conditions are satisfied: @@ -91,7 +93,9 @@ The ``wrap`` class - the :ref:`reference interface ` is default-initialisable; - the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` (see the :cpp:concept:`iface_with_impl` concept); - - *args* can be perfectly-forwarded to construct an instance of the value type :cpp:type:`T`. + - :cpp:var:`args` can be perfectly-forwarded to construct an instance of the value type :cpp:type:`T`. + + :param args: the input construction arguments. :throws: any exception thrown by the default constructor of the interface implementation or of the :ref:`reference interface `, by the construction of the value type or by memory allocation errors @@ -103,30 +107,32 @@ The ``wrap`` class Copy constructor. - When employing value semantics, the copy constructor will copy-construct the type-erased value from *other*. - Otherwise, a :cpp:class:`wrap` sharing ownership of the type-erased value with *other* will be constructed. + When employing value semantics, the copy constructor will copy-construct the type-erased value from :cpp:var:`other`. + Otherwise, a :cpp:class:`wrap` sharing ownership of the type-erased value with :cpp:var:`other` will be constructed. - This constructor is enabled only if *all* the following conditions are satisfied: + This constructor is enabled only if the following conditions are satisfied: - the :ref:`reference interface ` is default-initialisable; - when employing value semantics, the :cpp:var:`~config::copyable` option in :cpp:var:`Cfg` is activated. + :param other: the :cpp:class:`wrap` to be copied. + :throws std\:\:invalid_argument: if the type-erased value is not copy-constructible and value semantics is being used. :throws: any exception thrown by the default constructor of the interface implementation or of the :ref:`reference interface `, by the copy-construction of the value type or by memory allocation errors - if the value type does not fit in static storage or if value semantics is being used. If it can be - determined at compile time that none of these conditions can occurr, then this constructor - is marked ``noexcept``. + if the value type does not fit in static storage or if value semantics is being used. This constructor + is marked ``noexcept`` when using reference semantics and if the :ref:`reference interface `'s + default constructor is marked ``noexcept``. .. cpp:function:: [[nodiscard]] friend bool is_invalid(const wrap &w) noexcept - This function will return ``true`` if *w* is in the :ref:`invalid state `, + This function will return ``true`` if :cpp:var:`w` is in the :ref:`invalid state `, ``false`` otherwise. :param w: the input argument. - :return: the validity status for *w*. + :return: the validity status for :cpp:var:`w`. .. cpp:function:: [[nodiscard]] friend const IFace *iface_ptr(const wrap &w) noexcept [[nodiscard]] friend const IFace *iface_ptr(const wrap &&w) noexcept @@ -137,7 +143,7 @@ The ``wrap`` class These functions will return a pointer to the instance of the interface :cpp:type:`IFace` stored within a :cpp:class:`wrap`. - If *w* is in the :ref:`invalid state `, then ``nullptr`` will be returned. + If :cpp:var:`w` is in the :ref:`invalid state `, then ``nullptr`` will be returned. :param w: the input argument. @@ -147,23 +153,23 @@ The ``wrap`` class 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 `). - It will then construct in *w* a value of type :cpp:type:`T` using the construction arguments :cpp:type:`Args`. + This function will first destroy the value in :cpp:var:`w` (if :cpp:var:`w` is not already in the :ref:`invalid state `). + It will then construct in :cpp:var:`w` a value of type :cpp:type:`T` using the construction arguments :cpp:type:`Args`. This function is enabled only if an instance of :cpp:type:`T` can be constructed from :cpp:type:`Args` and if the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` (see the :cpp:concept:`iface_with_impl` concept). - Passing *w* as an argument in *args* (e.g., attempting to emplace *w* into itself) will lead to + Passing :cpp:var:`w` as an argument in :cpp:var:`args` (e.g., attempting to emplace :cpp:var:`w` into itself) will lead to undefined behaviour. This function is ``noexcept`` if all these conditions are satisfied: - - *w* is using value semantics, - - the static size and alignment of *w* are :ref:`large enough ` to store an instance of :cpp:type:`T`, + - :cpp:var:`w` is using value semantics, + - the static size and alignment of :cpp:var:`w` are :ref:`large enough ` to store an instance of :cpp:type:`T`, - the invoked constructor of :cpp:type:`T` does not throw. - If an exception is thrown, *w* may be left in the :ref:`invalid state `. + If an exception is thrown, :cpp:var:`w` may be left in the :ref:`invalid state `. :param w: the target :cpp:class:`wrap`. :param args: the construction arguments. @@ -177,16 +183,16 @@ The ``wrap`` class :param w: the input :cpp:class:`wrap`. - :return: ``true`` if *w* is currently employing static storage, ``false`` otherwise. + :return: ``true`` if :cpp:var:`w` is currently employing static storage, ``false`` otherwise. .. cpp:function:: [[nodiscard]] bool is_valid(const wrap &w) noexcept - This function will return ``false`` if *w* is in the :ref:`invalid state `, + This function will return ``false`` if :cpp:var:`w` is in the :ref:`invalid state `, ``true`` otherwise. :param w: the input argument. - :return: the validity status for *w*. + :return: the validity status for :cpp:var:`w`. .. cpp:function:: template bool has_dynamic_storage(const wrap &w) noexcept @@ -194,7 +200,7 @@ The ``wrap`` class :param w: the input :cpp:class:`wrap`. - :return: ``true`` if *w* is currently employing dynamic storage, ``false`` otherwise. + :return: ``true`` if :cpp:var:`w` is currently employing dynamic storage, ``false`` otherwise. .. cpp:struct:: invalid_wrap_t diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 4c31509..a50875a 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -270,7 +270,7 @@ struct TANUKI_VISIBLE value_iface : public IFace, value_iface_base { virtual ~value_iface() = default; // NOTE: don't make these pure virtual as this prevents us from asserting - // default-constructability of the implementation interface when doing concept + // default-constructibility of the implementation interface when doing concept // checking. // LCOV_EXCL_START @@ -631,11 +631,16 @@ struct TANUKI_VISIBLE holder final : public detail::impl_from_iface; } + // Copy/move construction primitives. + // Clone this, and cast the result to the value interface. [[nodiscard]] detail::value_iface *_tanuki_clone_holder() const final { - // NOTE: don't use std::copy_constructible as that requires + // NOTE: don't use std::copy_constructible, as that requires // the ability to move-construct. + // NOTE: we don't need to check the constructibility of the holder + // object: since the holder object already exists, we already know that + // there is a default-constructible interface implementation. if constexpr (std::is_copy_constructible_v) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) return new holder(m_value); @@ -684,6 +689,9 @@ struct TANUKI_VISIBLE holder final : public detail::impl_from_iface *v_iface) const final { From c127832a52ef8c92f80a40ff5f78885be19106f4 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 17 May 2024 10:18:42 +0200 Subject: [PATCH 03/15] Several changes: move runtime errors at compile time, enforce copyable/movable/swappable consistency between wrap and value. Push early for CI. --- include/tanuki/tanuki.hpp | 157 +++++++++++++++++++++++++++-------- test/ranges.hpp | 3 +- test/sqlite_ts.cpp | 2 +- test/test_basics.cpp | 24 ++---- test/test_gen_assignment.cpp | 17 ---- test/test_noncopyable.cpp | 19 +---- test/test_nostatic.cpp | 3 +- tutorial/emplace.cpp | 2 +- 8 files changed, 139 insertions(+), 88 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index a50875a..b707da2 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -348,6 +348,18 @@ struct TANUKI_VISIBLE value_iface : public IFace, value_iface_base { #if defined(TANUKI_WITH_BOOST_S11N) + // NOTE: these are used to check that a value type is serialisable. + [[nodiscard]] virtual bool _tanuki_value_is_default_initializable() const noexcept + { + unreachable(); + assert(false); + } + [[nodiscard]] virtual bool _tanuki_value_is_move_constructible() const noexcept + { + unreachable(); + assert(false); + } + private: // Serialization. friend class boost::serialization::access; @@ -636,17 +648,18 @@ struct TANUKI_VISIBLE holder final : public detail::impl_from_iface *_tanuki_clone_holder() const final { - // NOTE: don't use std::copy_constructible, as that requires - // the ability to move-construct. // NOTE: we don't need to check the constructibility of the holder // object: since the holder object already exists, we already know that // there is a default-constructible interface implementation. if constexpr (std::is_copy_constructible_v) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) return new holder(m_value); - } else { - throw std::invalid_argument("Attempting to clone a non-copyable value type"); } + + // NOTE: we should never reach this point as we are using this function + // only with value semantics and we are forbidding + // the creation of a copyable value semantics wrap from a non-copyable value. + detail::unreachable(); // LCOV_EXCL_LINE } // Same as above, but return a shared ptr. [[nodiscard]] std::shared_ptr> _tanuki_shared_clone_holder() const final @@ -654,6 +667,12 @@ struct TANUKI_VISIBLE holder final : public detail::impl_from_iface) { return std::make_shared(m_value); } else { + // NOTE: this is the one case in which we might end up here at runtime. This function + // is used only in the implementation of the copy() function to force deep copy + // behaviour when employing reference semantics. But, when reference semantics is active, + // we are always allowing the construction of a wrap regardless of the copyability + // of the value type. Hence, we might end up attempting to deep-copy a wrap containing + // a non-copyable value. throw std::invalid_argument("Attempting to clone a non-copyable value type"); } } @@ -668,15 +687,16 @@ struct TANUKI_VISIBLE holder final : public detail::impl_from_iface * - // NOLINTNEXTLINE(bugprone-exception-escape) - _tanuki_move_init_holder(void *ptr) && noexcept final + [[nodiscard]] detail::value_iface *_tanuki_move_init_holder(void *ptr) && noexcept final { if constexpr (std::is_move_constructible_v) { #if defined(TANUKI_WITH_BOOST_S11N) @@ -685,9 +705,12 @@ struct TANUKI_VISIBLE holder final : public detail::impl_from_iface_tanuki_value_type_index()); *static_cast(v_iface->_tanuki_value_ptr()) = m_value; - } else { - throw std::invalid_argument("Attempting to copy-assign a non-copyable value type"); + return; } + + // NOTE: we should never reach this point as we are using this function + // only with value semantics and we are forbidding + // the creation of a copyable value semantics wrap from a non-copyable value. + detail::unreachable(); // LCOV_EXCL_LINE } // Move-assign m_value into the m_value of v_iface. - // NOLINTNEXTLINE(bugprone-exception-escape) void _tanuki_move_assign_value_to(detail::value_iface *v_iface) && noexcept final { if constexpr (std::is_move_assignable_v) { assert(typeid(T) == v_iface->_tanuki_value_type_index()); *static_cast(v_iface->_tanuki_value_ptr()) = std::move(m_value); - } else { - throw std::invalid_argument("Attempting to move-assign a non-movable value type"); // LCOV_EXCL_LINE + return; } + + // NOTE: we should never reach this point as we are using this function + // only with value semantics and we are forbidding + // the creation of a movable value semantics wrap from a non-movable value. + detail::unreachable(); // LCOV_EXCL_LINE } // Copy-assign the object of type T assumed to be stored in ptr into m_value. void _tanuki_copy_assign_value_from(const void *ptr) final { if constexpr (std::is_copy_assignable_v) { m_value = *static_cast(ptr); - } else { - throw std::invalid_argument("Attempting to copy-assign a non-copyable value type"); + return; } + + // NOTE: we should never reach this point as we are using this function + // only with value semantics and we are forbidding + // the creation of a copyable value semantics wrap from a non-copyable value. + detail::unreachable(); // LCOV_EXCL_LINE } - // NOLINTNEXTLINE(bugprone-exception-escape) void _tanuki_move_assign_value_from(void *ptr) noexcept final { if constexpr (std::is_move_assignable_v) { m_value = std::move(*static_cast(ptr)); - } else { - throw std::invalid_argument("Attempting to move-assign a non-movable value type"); // LCOV_EXCL_LINE + return; } + + // NOTE: we should never reach this point as we are using this function + // only with value semantics and we are forbidding + // the creation of a movable value semantics wrap from a non-movable value. + detail::unreachable(); // LCOV_EXCL_LINE } // Swap m_value with the m_value of v_iface. - // NOLINTNEXTLINE(bugprone-exception-escape) void _tanuki_swap_value(detail::value_iface *v_iface) noexcept final { if constexpr (std::swappable) { @@ -745,13 +781,27 @@ struct TANUKI_VISIBLE holder final : public detail::impl_from_iface(v_iface->_tanuki_value_ptr())); - } else { - throw std::invalid_argument("Attempting to swap a non-swappable value type"); // LCOV_EXCL_LINE + + return; } + + // NOTE: we should never reach this point as we are using this function + // only with value semantics and we are forbidding + // the creation of a swappable value semantics wrap from a non-swappable value. + detail::unreachable(); // LCOV_EXCL_LINE } #if defined(TANUKI_WITH_BOOST_S11N) + [[nodiscard]] bool _tanuki_value_is_default_initializable() const noexcept final + { + return std::default_initializable; + } + [[nodiscard]] bool _tanuki_value_is_move_constructible() const noexcept final + { + return std::move_constructible; + } + // Serialization. friend class boost::serialization::access; template @@ -970,7 +1020,11 @@ concept valid_config = // Cfg.explicit_ctor must be set to one of the valid enumerators. (Cfg.explicit_ctor >= wrap_ctor::always_explicit && Cfg.explicit_ctor <= wrap_ctor::always_implicit) && // Cfg.semantics must be one of the two valid enumerators. - (Cfg.semantics == wrap_semantics::value || Cfg.semantics == wrap_semantics::reference); + (Cfg.semantics == wrap_semantics::value || Cfg.semantics == wrap_semantics::reference) && + // If the wrap is to be copyable, then it must also be movable. + (!Cfg.copyable || Cfg.movable) && + // If the wrap is to be movable, then it must also be swappable. + (!Cfg.movable || Cfg.swappable); // Helpers to ease the definition of a reference interface. #define TANUKI_REF_IFACE_MEMFUN(name) \ @@ -1145,6 +1199,15 @@ struct get_ref_iface { template using get_ref_iface_t = typename get_ref_iface, Wrap>::type; +// Concept to check that a value type T +// is consistent with the wrap settings in Cfg: +// if the wrap is using value semantics and it is +// copyable/movable/swappable, so must be the value type. +template +concept copy_move_swap_consistent = (Cfg.semantics == wrap_semantics::reference) + || ((!Cfg.copyable || std::copyable) && (!Cfg.movable || std::movable) + && (!Cfg.swappable || std::swappable)); + } // namespace detail // The wrap class. @@ -1248,6 +1311,20 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface != nullptr && !this->m_pv_iface->_tanuki_value_is_default_initializable()) [[unlikely]] { + throw std::invalid_argument("Cannot serialise a wrap containing a value of type '" + + demangle(this->m_pv_iface->_tanuki_value_type_index().name()) + + "': the type is not default-initializable"); + } + + if constexpr (Cfg.semantics == wrap_semantics::value) { + if (this->m_pv_iface != nullptr && !this->m_pv_iface->_tanuki_value_is_move_constructible()) [[unlikely]] { + throw std::invalid_argument("Cannot serialise a wrap containing a value of type '" + + demangle(this->m_pv_iface->_tanuki_value_type_index().name()) + + "': the type is not move-constructible"); + } + } + if constexpr (Cfg.semantics == wrap_semantics::value && Cfg.static_size > 0u) { // Store the storage type. ar << stype(); @@ -1384,7 +1461,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage) && // We must be able to construct the holder. - detail::holder_constructible_from + detail::holder_constructible_from && + // Check copy/move/swap consistency. + detail::copy_move_swap_consistent wrap() noexcept(noexcept(this->ctor_impl()) && detail::nothrow_default_initializable) { ctor_impl(); @@ -1400,7 +1479,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage, wrap>) && // We must be able to construct the holder. - detail::holder_constructible_from, IFace, Cfg.semantics, T &&> + detail::holder_constructible_from, IFace, Cfg.semantics, T &&> && + // Check copy/move/swap consistency. + detail::copy_move_swap_consistent, Cfg> explicit(explicit_ctor < wrap_ctor::always_implicit) // NOLINTNEXTLINE(bugprone-forwarding-reference-overload,google-explicit-constructor,hicpp-explicit-conversions) wrap(T &&x) noexcept(noexcept(this->ctor_impl>(std::forward(x))) @@ -1413,6 +1494,8 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage requires std::default_initializable && // We must be able to construct the holder. @@ -1435,7 +1518,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage && std::same_as> && std::default_initializable && // We must be able to construct the holder. - detail::holder_constructible_from + detail::holder_constructible_from && + // Check copy/move consistency. + detail::copy_move_swap_consistent explicit wrap(std::in_place_type_t, U &&...args) noexcept(noexcept(this->ctor_impl(std::forward(args)...)) && detail::nothrow_default_initializable) @@ -1636,16 +1721,14 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage requires - // NOTE: not 100% sure about this, but it seems consistent - // for generic assignment to be enabled only if copy/move - // assignment are as well. - ((Cfg.copyable && Cfg.movable) || Cfg.semantics == wrap_semantics::reference) && // Make extra sure this does not compete with the invalid assignment operator. (!std::same_as>) && // Must not compete with copy/move assignment. (!std::same_as, wrap>) && // We must be able to construct the holder. - detail::holder_constructible_from, IFace, Cfg.semantics, T &&> + detail::holder_constructible_from, IFace, Cfg.semantics, T &&> && + // Check copy/move consistency. + detail::copy_move_swap_consistent, Cfg> wrap &operator=(T &&x) { if constexpr (Cfg.semantics == wrap_semantics::value) { @@ -1708,9 +1791,13 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage requires + // T must be an a non-cv-qualified object. + std::is_object_v && std::same_as> && // We must be able to construct the holder. - detail::holder_constructible_from - friend void emplace(wrap &w, Args &&...args) noexcept(noexcept(w.ctor_impl(std::forward(args)...))) + detail::holder_constructible_from && + // Check copy/move consistency. + detail::copy_move_swap_consistent + friend void emplace(wrap &w, Args &&...args) noexcept(noexcept(w.ctor_impl(std::forward(args)...))) { if constexpr (Cfg.semantics == wrap_semantics::value) { // Destroy the value in w if necessary. diff --git a/test/ranges.hpp b/test/ranges.hpp index 0cb176b..8fa9a81 100644 --- a/test/ranges.hpp +++ b/test/ranges.hpp @@ -311,7 +311,8 @@ inline constexpr auto generic_range_config = tanuki::config, generic_range_iface>, .static_align = tanuki::holder_align, generic_range_iface>, - .pointer_interface = false}; + .pointer_interface = false, + .copyable = false}; template typename It> diff --git a/test/sqlite_ts.cpp b/test/sqlite_ts.cpp index a3b91f9..c658695 100644 --- a/test/sqlite_ts.cpp +++ b/test/sqlite_ts.cpp @@ -182,7 +182,7 @@ struct sqlite_ts { } } sqlite_ts &operator=(const sqlite_ts &) = delete; - sqlite_ts &operator=(sqlite_ts &&) noexcept = delete; + sqlite_ts &operator=(sqlite_ts &&) noexcept = default; ~sqlite_ts() { if (db != nullptr) { diff --git a/test/test_basics.cpp b/test/test_basics.cpp index bddb05c..a47cca0 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -100,16 +100,6 @@ TANUKI_S11N_WRAP_EXPORT2(small2, "small2", any_iface) #endif -struct copythrow { - copythrow() = default; - // NOLINTNEXTLINE - copythrow(const copythrow &) {}; - copythrow(copythrow &&) noexcept = delete; - copythrow &operator=(const copythrow &) = delete; - copythrow &operator=(copythrow &&) noexcept = delete; - ~copythrow() = default; -}; - void my_func(int) {} TEST_CASE("basics") @@ -148,13 +138,14 @@ TEST_CASE("basics") // noexcept handling for the value ctor. REQUIRE(std::is_nothrow_constructible_v); - REQUIRE(std::is_constructible_v); - REQUIRE(!std::is_nothrow_constructible_v); + // TODO update tests. + // REQUIRE(std::is_constructible_v); + // REQUIRE(!std::is_nothrow_constructible_v); // noexcept handling for the emplace ctor. REQUIRE(std::is_nothrow_constructible_v, int>); - REQUIRE(std::is_constructible_v, const copythrow &>); - REQUIRE(!std::is_nothrow_constructible_v, const copythrow &>); + // REQUIRE(std::is_constructible_v, const copythrow &>); + // REQUIRE(!std::is_nothrow_constructible_v, const copythrow &>); // Value ctor explicit by default. REQUIRE(!std::is_convertible_v); @@ -184,8 +175,9 @@ TEST_CASE("basics") REQUIRE(is_invalid(w2)); // NOLINTEND - // Emplace test with class which is not copyable/movable. - wrap_t w_mut(std::in_place_type); + // Emplace test with class which is not copyable/movable/swappable. + wrap w_mut( + std::in_place_type); REQUIRE(noexcept(wrap_t(std::in_place_type))); // Check throwing in value_ref. diff --git a/test/test_gen_assignment.cpp b/test/test_gen_assignment.cpp index 1c9674c..f3b5733 100644 --- a/test/test_gen_assignment.cpp +++ b/test/test_gen_assignment.cpp @@ -58,16 +58,6 @@ struct cm_throw { } }; -struct copythrow { - copythrow() = default; - // NOLINTNEXTLINE - copythrow(const copythrow &) {}; - copythrow(copythrow &&) noexcept = delete; - copythrow &operator=(const copythrow &) = delete; - copythrow &operator=(copythrow &&) noexcept = delete; - ~copythrow() = default; -}; - void my_func1(int) {} void my_func2(int) {} @@ -131,13 +121,6 @@ TEST_CASE("gen assignment") cm_throw cmt; REQUIRE_THROWS_MATCHES(w1 = cmt, std::invalid_argument, Message("copy assignment")); } - { - wrap_t w1(std::in_place_type); - auto w2 = w1; - copythrow ct; - REQUIRE_THROWS_MATCHES(w1 = std::as_const(ct), std::invalid_argument, - Message("Attempting to copy-assign a non-copyable value type")); - } // Special handling for function types. { diff --git a/test/test_noncopyable.cpp b/test/test_noncopyable.cpp index a483336..c20f3fe 100644 --- a/test/test_noncopyable.cpp +++ b/test/test_noncopyable.cpp @@ -1,9 +1,8 @@ -#include +#include #include #include -#include #if defined(__GNUC__) @@ -38,22 +37,10 @@ struct noncopyable { TEST_CASE("noncopyable") { - using Catch::Matchers::Message; - using wrap_t = tanuki::wrap; - wrap_t w(noncopyable<1>{}); - - REQUIRE_THROWS_MATCHES(wrap_t(w), std::invalid_argument, - Message("Attempting to copy-construct a non-copyable value type")); - - w = wrap_t(noncopyable<100>{}); - - REQUIRE_THROWS_MATCHES(wrap_t(w), std::invalid_argument, Message("Attempting to clone a non-copyable value type")); - - wrap_t w2(noncopyable<100>{}); - REQUIRE_THROWS_MATCHES(w2 = w, std::invalid_argument, - Message("Attempting to copy-assign a non-copyable value type")); + REQUIRE(!std::constructible_from>); + REQUIRE(!std::constructible_from>); } // NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) diff --git a/test/test_nostatic.cpp b/test/test_nostatic.cpp index 54c1bd2..ec3e1f3 100644 --- a/test/test_nostatic.cpp +++ b/test/test_nostatic.cpp @@ -130,7 +130,8 @@ TEST_CASE("basics") // NOLINTEND // Emplace test with class which is not copyable/movable. - wrap_t w_mut(std::in_place_type); + tanuki::wrap + w_mut(std::in_place_type); REQUIRE(!noexcept(wrap_t(std::in_place_type))); // Check throwing in value_ref. diff --git a/tutorial/emplace.cpp b/tutorial/emplace.cpp index cfbaacd..eeeb1cf 100644 --- a/tutorial/emplace.cpp +++ b/tutorial/emplace.cpp @@ -14,7 +14,7 @@ struct any_iface { int main() { - using any_wrap = tanuki::wrap; + using any_wrap = tanuki::wrap; // Emplace-construct with std::mutex. any_wrap w(std::in_place_type); From 6605d59b94636eae7b01c6eebbf227ba6c9b71ac Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 17 May 2024 10:36:27 +0200 Subject: [PATCH 04/15] clang 14 test fixes. --- test/test_basics.cpp | 2 +- test/test_nostatic.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_basics.cpp b/test/test_basics.cpp index a47cca0..8d6c1e6 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -176,7 +176,7 @@ TEST_CASE("basics") // NOLINTEND // Emplace test with class which is not copyable/movable/swappable. - wrap w_mut( + wrap{.copyable = false, .movable = false, .swappable = false}> w_mut( std::in_place_type); REQUIRE(noexcept(wrap_t(std::in_place_type))); diff --git a/test/test_nostatic.cpp b/test/test_nostatic.cpp index ec3e1f3..496064a 100644 --- a/test/test_nostatic.cpp +++ b/test/test_nostatic.cpp @@ -130,7 +130,7 @@ TEST_CASE("basics") // NOLINTEND // Emplace test with class which is not copyable/movable. - tanuki::wrap + tanuki::wrap{.static_size = 0, .copyable = false, .movable = false, .swappable = false}> w_mut(std::in_place_type); REQUIRE(!noexcept(wrap_t(std::in_place_type))); From 957acb51e750ef6660139614e9285fa59cf6da2d Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 09:44:23 +0200 Subject: [PATCH 05/15] Small coverage fix. --- include/tanuki/tanuki.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index b707da2..482d685 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -344,7 +344,6 @@ struct TANUKI_VISIBLE value_iface : public IFace, value_iface_base { unreachable(); assert(false); } - // LCOV_EXCL_STOP #if defined(TANUKI_WITH_BOOST_S11N) @@ -360,6 +359,8 @@ struct TANUKI_VISIBLE value_iface : public IFace, value_iface_base { assert(false); } + // LCOV_EXCL_STOP + private: // Serialization. friend class boost::serialization::access; From 36f5fc62469dfd6bfe939d472ae35357ef115dd2 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 10:32:15 +0200 Subject: [PATCH 06/15] Test additions. --- test/CMakeLists.txt | 2 +- test/test_basics.cpp | 12 ++++ test/test_no_copy_move_swap.cpp | 113 ++++++++++++++++++++++++++++++++ test/test_noncopyable.cpp | 52 --------------- 4 files changed, 126 insertions(+), 53 deletions(-) create mode 100644 test/test_no_copy_move_swap.cpp delete mode 100644 test/test_noncopyable.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index be62a61..eb415ed 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,7 +35,7 @@ target_link_libraries(tanuki_sqlite PRIVATE ${CMAKE_DL_LIBS} Threads::Threads) ADD_TANUKI_TESTCASE(test_basics) ADD_TANUKI_TESTCASE(test_swap) ADD_TANUKI_TESTCASE(test_defctor) -ADD_TANUKI_TESTCASE(test_noncopyable) +ADD_TANUKI_TESTCASE(test_no_copy_move_swap) ADD_TANUKI_TESTCASE(test_nostatic) ADD_TANUKI_TESTCASE(test_misc_utils) ADD_TANUKI_TESTCASE(test_gen_assignment) diff --git a/test/test_basics.cpp b/test/test_basics.cpp index 8d6c1e6..f030c44 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -92,6 +92,9 @@ struct small2 { } }; +template +concept emplaceable = requires(W &w, Args &&...args) { emplace(w, std::forward(args)...); }; + #if defined(TANUKI_WITH_BOOST_S11N) TANUKI_S11N_WRAP_EXPORT(large, any_iface) @@ -216,6 +219,15 @@ TEST_CASE("basics") using wrap2_t = wrap; wrap2_t w2inl{std::string{"foo"}}; REQUIRE(value_ref(w2inl) == "foo"); + + // Check that we cannot emplace/in-place init with invalid types. + REQUIRE(!std::constructible_from>); + REQUIRE(!std::constructible_from>); + REQUIRE(!std::constructible_from>); + + REQUIRE(!emplaceable); + REQUIRE(!emplaceable); + REQUIRE(!emplaceable); } TEST_CASE("assignment") diff --git a/test/test_no_copy_move_swap.cpp b/test/test_no_copy_move_swap.cpp new file mode 100644 index 0000000..70c4210 --- /dev/null +++ b/test/test_no_copy_move_swap.cpp @@ -0,0 +1,113 @@ +#include +#include + +#include + +#include +#include + +#if defined(__GNUC__) + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + +#endif + +// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) + +template +struct any_iface_impl : Base { +}; + +// NOLINTNEXTLINE +struct any_iface { + template + using impl = any_iface_impl; +}; + +template +concept emplaceable = requires(W &w, Args &&...args) { emplace(w, std::forward(args)...); }; + +struct noncopyable { + noncopyable() = default; + noncopyable(const noncopyable &) = delete; + noncopyable(noncopyable &&) noexcept = default; + noncopyable &operator=(const noncopyable &) = delete; + noncopyable &operator=(noncopyable &&) noexcept = default; + ~noncopyable() = default; + + int m_value = {}; +}; + +TEST_CASE("noncopyable") +{ + using wrap_t = tanuki::wrap; + + REQUIRE(!std::constructible_from); + REQUIRE(!std::constructible_from>); + REQUIRE(!emplaceable); + + REQUIRE(!tanuki::valid_config{.copyable = true, .movable = false}>); + REQUIRE(!tanuki::valid_config{.copyable = true, .movable = true, .swappable = false}>); + + using wrap2_t = tanuki::wrap{.copyable = false}>; + + REQUIRE(std::constructible_from); + REQUIRE(std::constructible_from>); + REQUIRE(emplaceable); + REQUIRE(!std::copyable); + REQUIRE(std::movable); + REQUIRE(std::swappable); + + using wrap3_t = tanuki::wrap{.semantics = tanuki::wrap_semantics::reference}>; + + REQUIRE(std::constructible_from); + REQUIRE(std::constructible_from>); + REQUIRE(emplaceable); + REQUIRE(std::copyable); + REQUIRE(std::movable); + REQUIRE(std::swappable); +} + +struct nonmovable { + nonmovable() = default; + nonmovable(const nonmovable &) = delete; + nonmovable(nonmovable &&) noexcept = delete; + nonmovable &operator=(const nonmovable &) = delete; + nonmovable &operator=(nonmovable &&) noexcept = delete; + ~nonmovable() = default; + + int m_value = {}; +}; + +TEST_CASE("nonmovable") +{ + using wrap_t = tanuki::wrap; + + REQUIRE(!std::constructible_from); + REQUIRE(!std::constructible_from>); + REQUIRE(!emplaceable); + + using wrap2_t = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; + + REQUIRE(!std::constructible_from); + REQUIRE(std::constructible_from>); + REQUIRE(emplaceable); + + using wrap3_t = tanuki::wrap{.semantics = tanuki::wrap_semantics::reference}>; + + REQUIRE(!std::constructible_from); + REQUIRE(std::constructible_from>); + REQUIRE(emplaceable); + REQUIRE(std::copyable); + REQUIRE(std::movable); + REQUIRE(std::swappable); +} + +// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) + +#if defined(__GNUC__) + +#pragma GCC diagnostic pop + +#endif diff --git a/test/test_noncopyable.cpp b/test/test_noncopyable.cpp deleted file mode 100644 index c20f3fe..0000000 --- a/test/test_noncopyable.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include - -#include - -#if defined(__GNUC__) - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" - -#endif - -// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) - -template -struct any_iface_impl : Base { -}; - -// NOLINTNEXTLINE -struct any_iface { - template - using impl = any_iface_impl; -}; - -template -struct noncopyable { - noncopyable() = default; - noncopyable(const noncopyable &) = delete; - noncopyable(noncopyable &&) noexcept = default; - noncopyable &operator=(const noncopyable &) = delete; - noncopyable &operator=(noncopyable &&) noexcept = default; - ~noncopyable() = default; - - int m_value[N] = {}; -}; - -TEST_CASE("noncopyable") -{ - using wrap_t = tanuki::wrap; - - REQUIRE(!std::constructible_from>); - REQUIRE(!std::constructible_from>); -} - -// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) - -#if defined(__GNUC__) - -#pragma GCC diagnostic pop - -#endif From 802e0fc941032d9d0001e519a22ebb7829dead73 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 13:13:06 +0200 Subject: [PATCH 07/15] Another warning fix. --- tutorial/emplace.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/emplace.cpp b/tutorial/emplace.cpp index eeeb1cf..880fa2d 100644 --- a/tutorial/emplace.cpp +++ b/tutorial/emplace.cpp @@ -14,7 +14,7 @@ struct any_iface { int main() { - using any_wrap = tanuki::wrap; + using any_wrap = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; // Emplace-construct with std::mutex. any_wrap w(std::in_place_type); From 1383250ee81bec508e69c78910c4168475183ccf Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 13:36:39 +0200 Subject: [PATCH 08/15] More testing. --- test/test_basics.cpp | 75 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/test/test_basics.cpp b/test/test_basics.cpp index f030c44..41e0776 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,8 @@ #include #include +#include +#include #if defined(__GNUC__) @@ -141,14 +144,23 @@ TEST_CASE("basics") // noexcept handling for the value ctor. REQUIRE(std::is_nothrow_constructible_v); - // TODO update tests. - // REQUIRE(std::is_constructible_v); - // REQUIRE(!std::is_nothrow_constructible_v); + REQUIRE(std::is_constructible_v); + REQUIRE(!std::is_nothrow_constructible_v); + REQUIRE(std::is_nothrow_constructible_v); + REQUIRE(std::is_constructible_v); + REQUIRE(std::is_nothrow_constructible_v); // noexcept handling for the emplace ctor. REQUIRE(std::is_nothrow_constructible_v, int>); - // REQUIRE(std::is_constructible_v, const copythrow &>); - // REQUIRE(!std::is_nothrow_constructible_v, const copythrow &>); + REQUIRE(!std::is_nothrow_constructible_v, const std::string &>); + REQUIRE(std::is_nothrow_constructible_v, std::string &&>); + REQUIRE(std::is_nothrow_constructible_v, int>); + + // noexcept handling for emplacement. + REQUIRE(noexcept(emplace(w1))); + REQUIRE(!noexcept(emplace(w1, "asdsada"))); + std::string tmp_str = "asda"; + REQUIRE(noexcept(emplace(w1, std::move(tmp_str)))); // Value ctor explicit by default. REQUIRE(!std::is_convertible_v); @@ -477,6 +489,59 @@ TEST_CASE("s11n invalid") REQUIRE(is_invalid(w)); } +struct nodefctor { + explicit nodefctor(int) {} + nodefctor() = delete; + nodefctor(nodefctor &&) noexcept = default; +}; + +TEST_CASE("s11n nodef") +{ + using Catch::Matchers::ContainsSubstring; + using Catch::Matchers::Message; + using Catch::Matchers::MessageMatches; + using Catch::Matchers::StartsWith; + + using wrap_t = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; + + wrap_t w(nodefctor{3}); + + std::stringstream ss; + + boost::archive::binary_oarchive oa(ss); + + REQUIRE_THROWS_MATCHES(oa << w, std::invalid_argument, + MessageMatches(StartsWith("Cannot serialise a wrap containing a value of type '"))); + REQUIRE_THROWS_MATCHES(oa << w, std::invalid_argument, + MessageMatches(ContainsSubstring("the type is not default-initializable"))); +} + +struct nomovector { + nomovector() = default; + nomovector(nomovector &&) noexcept = delete; +}; + +TEST_CASE("s11n nomove") +{ + using Catch::Matchers::ContainsSubstring; + using Catch::Matchers::Message; + using Catch::Matchers::MessageMatches; + using Catch::Matchers::StartsWith; + + using wrap_t = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; + + wrap_t w(std::in_place_type); + + std::stringstream ss; + + boost::archive::binary_oarchive oa(ss); + + REQUIRE_THROWS_MATCHES(oa << w, std::invalid_argument, + MessageMatches(StartsWith("Cannot serialise a wrap containing a value of type '"))); + REQUIRE_THROWS_MATCHES(oa << w, std::invalid_argument, + MessageMatches(ContainsSubstring("the type is not move-constructible"))); +} + #endif // NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) From 8dd48821e74767ac5c2716530e31e451f9b14dc7 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 14:56:08 +0200 Subject: [PATCH 09/15] More WIP on docs/testing. --- doc/config.rst | 6 ++++- doc/custom_construct.rst | 57 ++++++++++++++++++++++++---------------- doc/wrap.rst | 4 ++- test/test_defctor.cpp | 24 +++++++++++++++++ 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/doc/config.rst b/doc/config.rst index 18ae7be..12a1d4a 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -114,4 +114,8 @@ Configuration options - :cpp:var:`Cfg` is an instance of the primary :cpp:class:`config` template, - :cpp:var:`config::static_align` is a power of two, - :cpp:var:`config::explicit_ctor` is one of the enumerators defined in :cpp:enum:`wrap_ctor`, - - :cpp:var:`config::semantics` is one of the enumerators defined in :cpp:enum:`wrap_semantics`. + - :cpp:var:`config::semantics` is one of the enumerators defined in :cpp:enum:`wrap_semantics`, + - if :cpp:var:`config::copyable` is set to ``true``, so is :cpp:var:`config::movable` (that is, + a copyable :cpp:class:`wrap` must also be movable), + - if :cpp:var:`config::movable` is set to ``true``, so is :cpp:var:`config::swappable` (that is, + a movable :cpp:class:`wrap` must also be swappable). diff --git a/doc/custom_construct.rst b/doc/custom_construct.rst index e476813..692a251 100644 --- a/doc/custom_construct.rst +++ b/doc/custom_construct.rst @@ -22,8 +22,6 @@ for which an empty state is meaningful. The other option is to specify a custom (i.e., non-``void``) :cpp:type:`~config::DefaultValueType` as first template argument in :cpp:struct:`config`, in which case the default constructor of :cpp:class:`wrap` will value-initialise an interal value of type :cpp:type:`~config::DefaultValueType`. -Note that a custom :cpp:type:`~config::DefaultValueType` must satisfy the requirements -of the interface being wrapped by :cpp:class:`wrap`. If both the :cpp:var:`config::invalid_default_ctor` option is activated and a custom :cpp:type:`~config::DefaultValueType` is specified, then the :cpp:var:`config::invalid_default_ctor` option @@ -61,6 +59,36 @@ Note that the invalid status is only about whether or not a :cpp:class:`wrap` contains a value -- a valid wrap could be containing a moved-from value, and it is up to the user to handle such an occurrence. +.. _wrap_construct: + +Customising :cpp:class:`wrap`'s constructors +-------------------------------------------- + +Beside the :ref:`default constructor `, it is also possible to customise +the behaviour of :cpp:class:`wrap`'s other constructors in a variety of ways. + +:cpp:class:`wrap`'s generic constructors, for instance, are by default ``explicit``, +but they can be made implicit by altering the :cpp:var:`config::explicit_ctor` +configuration setting. If :cpp:var:`config::explicit_ctor` is set to +:cpp:enumerator:`wrap_ctor::ref_implicit`, then generic construction is implicit +when :ref:`wrapping a reference `, ``explicit`` otherwise. +If :cpp:var:`config::explicit_ctor` is set to +:cpp:enumerator:`wrap_ctor::always_implicit`, then generic construction is always implicit. + +By default, :cpp:class:`wrap` is copy/move constructible and copy/move assignable. +The copy/move constructors and assignment operators can be disabled by switching off +the :cpp:var:`config::copyable` and :cpp:var:`config::movable` configuration settings. +The :cpp:class:`wrap` is also by default `swappable `__. +Swappability can be switched on/off via the :cpp:var:`config::swappable` configuration setting. + +Copyability, movability and swappability are subject to several sanity checks and constraints. +Specifically: + +- a :cpp:var:`~config::copyable` :cpp:class:`wrap` must also be :cpp:var:`~config::movable`, + and a :cpp:var:`~config::movable` :cpp:class:`wrap` must also be :cpp:var:`~config::swappable`; +- when employing value semantics (the default), it is not possible to create a copyable/movable/swappable + :cpp:class:`wrap` containing a value type which is not also copyable/movable/swappable. + .. _emplacement: Emplacement @@ -93,7 +121,10 @@ We can emplace-construct a :cpp:class:`wrap` containing a ``std::mutex``: Note that in this specific case the variadic pack is empty (as the constructor of ``std::mutex`` takes no arguments) and thus only the ``std::in_place_type_t`` argument -is required. +is required. Note also that we used a custom configuration in which we disabled copy/move/swap +operations: this is necessary because ``std::mutex`` cannot be copied/moved/swapped, and thus, +as explained in the :ref:`previous section `, the :cpp:class:`wrap`'s copy/move/swap primitives must +also be disabled. In addition to the emplace constructor, the :cpp:func:`~wrap::emplace()` function is also available to emplace-assign a value into an existing :cpp:class:`wrap` object. Here @@ -103,23 +134,3 @@ is a simple example: :language: c++ :lines: 22-26 -Customising :cpp:class:`wrap`'s constructors --------------------------------------------- - -Beside the :ref:`default constructor `, it is also possible to customise -the behaviour of :cpp:class:`wrap`'s other constructors in a variety of ways. - -:cpp:class:`wrap`'s generic constructors, for instance, are by default ``explicit``, -but they can be made implicit by altering the :cpp:var:`config::explicit_ctor` -configuration setting. If :cpp:var:`config::explicit_ctor` is set to -:cpp:enumerator:`wrap_ctor::ref_implicit`, then generic construction is implicit -when :ref:`wrapping a reference `, ``explicit`` otherwise. -If :cpp:var:`config::explicit_ctor` is set to -:cpp:enumerator:`wrap_ctor::always_implicit`, then generic construction is always implicit. - -By default, :cpp:class:`wrap` is copy/move constructible and copy/move assignable. -The copy/move constructors and assignment operators can be disabled by switching off -the :cpp:var:`config::copyable` and :cpp:var:`config::movable` configuration settings. - -The :cpp:class:`wrap` is also by default `swappable `__. -Swappability can be switched on/off via the :cpp:var:`config::swappable` configuration setting. diff --git a/doc/wrap.rst b/doc/wrap.rst index 5544ddc..7851efd 100644 --- a/doc/wrap.rst +++ b/doc/wrap.rst @@ -17,7 +17,9 @@ The ``wrap`` class - a non-``void`` default-initialisable :cpp:type:`~config::DefaultValueType` with a valid, default-initialisable interface implementation for :cpp:type:`IFace` has been specified as first template argument in :cpp:var:`Cfg`. In this case, the default constructor value-initialises an instance of :cpp:type:`~config::DefaultValueType` as - the internal type-erased value. + the internal type-erased value. Note that the copyability, movability and swappability of + :cpp:type:`~config::DefaultValueType` must be consistent with the corresponding settings + in :cpp:var:`Cfg` in order for this constructor to be enabled. In both cases, the :ref:`reference interface ` must be default-initialisable in order for this constructor to be available. diff --git a/test/test_defctor.cpp b/test/test_defctor.cpp index f6e97a4..77d4869 100644 --- a/test/test_defctor.cpp +++ b/test/test_defctor.cpp @@ -20,6 +20,16 @@ struct bar { bar() = delete; }; +// Move-only struct. +struct baz { + baz() = default; + baz(const baz &) = delete; + baz(baz &&) noexcept = default; + baz &operator=(const baz &) = delete; + baz &operator=(baz &&) noexcept = default; + ~baz() = default; +}; + template struct any_iface_impl { }; @@ -80,6 +90,20 @@ TEST_CASE("def value type") using wrap4_t = tanuki::wrap{}>; REQUIRE(!std::default_initializable); + + // Try with a default value type whose copy/move/swap-ability do not + // match the config settings. + using wrap5_t = tanuki::wrap{}>; + + REQUIRE(!std::default_initializable); + + using wrap6_t = tanuki::wrap{.copyable = false}>; + + REQUIRE(std::default_initializable); + + using wrap7_t = tanuki::wrap{.copyable = false, .movable = false}>; + + REQUIRE(std::default_initializable); } // NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) From bb6c3962baeef64e62eeea15bd653331c9390652 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 20:15:13 +0200 Subject: [PATCH 10/15] Small tweak. --- doc/wrap.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/wrap.rst b/doc/wrap.rst index 7851efd..ac64093 100644 --- a/doc/wrap.rst +++ b/doc/wrap.rst @@ -17,7 +17,7 @@ The ``wrap`` class - a non-``void`` default-initialisable :cpp:type:`~config::DefaultValueType` with a valid, default-initialisable interface implementation for :cpp:type:`IFace` has been specified as first template argument in :cpp:var:`Cfg`. In this case, the default constructor value-initialises an instance of :cpp:type:`~config::DefaultValueType` as - the internal type-erased value. Note that the copyability, movability and swappability of + the internal type-erased value. When employing value semantics, the copyability, movability and swappability of :cpp:type:`~config::DefaultValueType` must be consistent with the corresponding settings in :cpp:var:`Cfg` in order for this constructor to be enabled. From 9c9b399c54c73e81a378ffa159569411b2088f2f Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 20:43:35 +0200 Subject: [PATCH 11/15] Another block of doc updates. --- doc/wrap.rst | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/doc/wrap.rst b/doc/wrap.rst index ac64093..b48a1fe 100644 --- a/doc/wrap.rst +++ b/doc/wrap.rst @@ -62,7 +62,9 @@ The ``wrap`` class - the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` (see the :cpp:concept:`iface_with_impl` concept); - - :cpp:var:`x` can be perfectly-forwarded to construct an instance of the value type. + - :cpp:var:`x` can be perfectly-forwarded to construct an instance of the value type; + - when employing value semantics, the copyability, movability and swappability of the value type + are consistent with the corresponding settings in :cpp:var:`Cfg`. This constructor is marked ``explicit`` if either: @@ -91,11 +93,13 @@ The ``wrap`` class This constructor is enabled only if *all* the following conditions are satisfied: - - :cpp:type:`T` is an object type without cv qualifications. + - :cpp:type:`T` is an object type without cv qualifications; - the :ref:`reference interface ` is default-initialisable; - the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` (see the :cpp:concept:`iface_with_impl` concept); - - :cpp:var:`args` can be perfectly-forwarded to construct an instance of the value type :cpp:type:`T`. + - :cpp:var:`args` can be perfectly-forwarded to construct an instance of the value type :cpp:type:`T`; + - when employing value semantics, the copyability, movability and swappability of the value type :cpp:type:`T` + are consistent with the corresponding settings in :cpp:var:`Cfg`. :param args: the input construction arguments. @@ -120,10 +124,9 @@ The ``wrap`` class :param other: the :cpp:class:`wrap` to be copied. - :throws std\:\:invalid_argument: if the type-erased value is not copy-constructible and value semantics is being used. :throws: any exception thrown by the default constructor of the interface implementation or of - the :ref:`reference interface `, by the copy-construction of the value type or by memory allocation errors - if the value type does not fit in static storage or if value semantics is being used. This constructor + the :ref:`reference interface `, or by the copy-construction of the value type or by memory allocation errors + when value semantics is being used. This constructor is marked ``noexcept`` when using reference semantics and if the :ref:`reference interface `'s default constructor is marked ``noexcept``. @@ -158,9 +161,14 @@ The ``wrap`` class This function will first destroy the value in :cpp:var:`w` (if :cpp:var:`w` is not already in the :ref:`invalid state `). It will then construct in :cpp:var:`w` a value of type :cpp:type:`T` using the construction arguments :cpp:type:`Args`. - This function is enabled only if an instance of :cpp:type:`T` can be constructed from :cpp:type:`Args` - and if the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` - (see the :cpp:concept:`iface_with_impl` concept). + This function is enabled only if the following conditions are satisfied: + + - :cpp:type:`T` is an object type without cv qualifications; + - an instance of :cpp:type:`T` can be constructed from :cpp:type:`Args`; + - the interface :cpp:type:`IFace` has a valid, default-initialisable implementation for the value type :cpp:type:`T` + (see the :cpp:concept:`iface_with_impl` concept); + - when employing value semantics, the copyability, movability and swappability of the value type :cpp:type:`T` + are consistent with the corresponding settings in :cpp:var:`Cfg`. Passing :cpp:var:`w` as an argument in :cpp:var:`args` (e.g., attempting to emplace :cpp:var:`w` into itself) will lead to undefined behaviour. From ea5849fb14a98423ee12512ae2a549834af68504 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 20:48:01 +0200 Subject: [PATCH 12/15] Another addition. --- doc/nonintrusive.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/nonintrusive.rst b/doc/nonintrusive.rst index 8f1c2aa..5087b57 100644 --- a/doc/nonintrusive.rst +++ b/doc/nonintrusive.rst @@ -27,6 +27,10 @@ a partial specialisation of the :cpp:struct:`iface_impl` struct for ``my_iface`` :language: c++ :lines: 17-29 +That is, the :cpp:struct:`iface_impl` struct template depends on four parameters: the first one is the +the interface for which we are providing an implementation, while the remaining three are the +customary ``Base``, ``Holder`` and ``T`` arguments whose meaning has been explained in previous tutorials. + Here we are specifying an implementation for all value types ``T``, but, as explained in previous tutorials, we could also easily provide a partially-specialised implementation, constrain the implementation only for value types modelling From 859a4e951f377f73d7f62cfd91d629bd43cae39b Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 18 May 2024 22:18:55 +0200 Subject: [PATCH 13/15] Cleanups, begin work on ref semantics tutorial. --- doc/ref_semantics.rst | 4 +++ doc/std_function.rst | 13 ++++----- doc/tutorials.rst | 1 + test/sentinel.hpp | 62 +++++++++++++++++-------------------------- 4 files changed, 34 insertions(+), 46 deletions(-) create mode 100644 doc/ref_semantics.rst diff --git a/doc/ref_semantics.rst b/doc/ref_semantics.rst new file mode 100644 index 0000000..c9faa14 --- /dev/null +++ b/doc/ref_semantics.rst @@ -0,0 +1,4 @@ +.. cpp:namespace-push:: tanuki + +Reference semantics +=================== diff --git a/doc/std_function.rst b/doc/std_function.rst index a819101..c7e479d 100644 --- a/doc/std_function.rst +++ b/doc/std_function.rst @@ -3,16 +3,13 @@ Implementing a ``std::function`` look-alike =========================================== -In this case study, we will be implementing (an approximation of) +In this case study, we will be implementing an approximation of ``std::function`` with tanuki. This will not be a full drop-in -replacement for ``std::function``, because there is a (small) part of the ``std::function`` API which cannot -currently be implemented with tanuki, and because we aim to write an implementation which, contrary to ``std::function`` +replacement for ``std::function``, because we aim to write an implementation which, contrary to ``std::function`` and similarly to `std::move_only_function `__, -correctly respects const-correctness. - -As a bonus though, our polymorphic function wrapper (which we will be naming ``callable``) will also -support wrapping references (thus mimicking -`std::function_ref `__ in some sense). +correctly respects const-correctness. Additionally, our polymorphic function wrapper (which we will be naming ``callable``) +will also support wrapping references (thus providing functionality similar to +`std::function_ref `__). The interface and its implementation ------------------------------------ diff --git a/doc/tutorials.rst b/doc/tutorials.rst index f3ddb87..96fa7d4 100644 --- a/doc/tutorials.rst +++ b/doc/tutorials.rst @@ -12,3 +12,4 @@ Tutorials custom_construct.rst composite_interfaces.rst nonintrusive.rst + ref_semantics.rst diff --git a/test/sentinel.hpp b/test/sentinel.hpp index bcc5e86..112a949 100644 --- a/test/sentinel.hpp +++ b/test/sentinel.hpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -26,23 +25,17 @@ namespace detail // Detect instances of std::reference_wrapper. template -struct is_reference_wrapper : std::false_type { -}; +inline constexpr bool is_reference_wrapper_v = false; template -struct is_reference_wrapper> : std::true_type { -}; +inline constexpr bool is_reference_wrapper_v> = true; // A type-erased interface for storing references to objects. -template - requires is_reference_wrapper::value -struct any_ref_iface_impl : public Base { -}; - -// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) struct any_ref_iface { template - using impl = any_ref_iface_impl; + requires is_reference_wrapper_v + struct impl : public Base { + }; }; inline constexpr auto any_ref_config @@ -94,40 +87,33 @@ concept has_distance_to_iter = requires(const T &x, const any_ref &ar) { // Detect instances of sentinel_box. template -struct is_sentinel_box : std::false_type { -}; +inline constexpr bool is_sentinel_box_v = false; template -struct is_sentinel_box> : std::true_type { -}; +inline constexpr bool is_sentinel_box_v> = true; -// Definition of the interface implementation for sentinel. -template - requires is_sentinel_box::value -struct sentinel_iface_impl : public Base { - [[nodiscard]] bool at_end(const any_ref &ar) const final - { - return getval(this).at_end(ar); - } - [[nodiscard]] std::ptrdiff_t distance_to_iter(const any_ref &ar) const final - { - if constexpr (has_distance_to_iter) { - return getval(this).distance_to_iter(ar); - } else { - throw std::runtime_error("The sentinel type '" + tanuki::demangle(typeid(T).name()) - + "' is not a sized sentinel"); - } - } -}; - -// Definition of the interface. -// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +// Sentinel interface. struct sentinel_iface { [[nodiscard]] virtual bool at_end(const any_ref &) const = 0; [[nodiscard]] virtual std::ptrdiff_t distance_to_iter(const any_ref &) const = 0; template - using impl = sentinel_iface_impl; + requires is_sentinel_box_v + struct impl : public Base { + [[nodiscard]] bool at_end(const any_ref &ar) const final + { + return getval(this).at_end(ar); + } + [[nodiscard]] std::ptrdiff_t distance_to_iter(const any_ref &ar) const final + { + if constexpr (has_distance_to_iter) { + return getval(this).distance_to_iter(ar); + } else { + throw std::runtime_error("The sentinel type '" + tanuki::demangle(typeid(T).name()) + + "' is not a sized sentinel"); + } + } + }; }; struct default_sentinel { From db641970450eb12d57a4f862d169b156720eb695 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 19 May 2024 14:22:08 +0200 Subject: [PATCH 14/15] More docs WIP. --- doc/config.rst | 10 ++++++++++ doc/hello_world.rst | 9 +++++---- doc/ref_semantics.rst | 30 ++++++++++++++++++++++++++++++ doc/wrap.rst | 24 ++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/doc/config.rst b/doc/config.rst index 12a1d4a..b310cc5 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -88,6 +88,16 @@ Configuration options .. cpp:enum-class:: wrap_semantics + Enumerator representing the semantics chosen for a :cpp:class:`wrap` class. + + .. cpp:enumerator:: value + + Value semantics. + + .. cpp:enumerator:: reference + + :ref:`Reference semantics `. + .. cpp:var:: template requires iface_with_impl inline constexpr std::size_t holder_size Helper to compute the total amount of memory (in bytes) needed to statically store in a :cpp:class:`wrap` diff --git a/doc/hello_world.rst b/doc/hello_world.rst index 0461053..f1dbd95 100644 --- a/doc/hello_world.rst +++ b/doc/hello_world.rst @@ -95,11 +95,12 @@ Although this code looks superficially similar to the OO-style approach, there a key differences: - no dynamic memory allocation is enforced: the :cpp:class:`wrap` class employs - an :ref:`optimisation ` that stores small values inline, + an :ref:`optimisation ` that stores small values inline; - there is no hierarchical coupling: objects of any destructible class can be - stored in an ``any_wrap`` without the need to inherit from anything, -- ``any_wrap`` employs value semantics (that is, its copy constructor will make a copy - of the internal value). + stored in an ``any_wrap`` without the need to inherit from anything; +- ``any_wrap`` employs (by default) value semantics (that is, copy/move/swap operations + on a :cpp:class:`wrap` will result in copying/moving/swapping the internal + value). And that's it for the most minimal example! diff --git a/doc/ref_semantics.rst b/doc/ref_semantics.rst index c9faa14..6685468 100644 --- a/doc/ref_semantics.rst +++ b/doc/ref_semantics.rst @@ -1,4 +1,34 @@ +.. _ref_semantics: + .. cpp:namespace-push:: tanuki Reference semantics =================== + +In the :ref:`first tutorial `, we mentioned how one of the reasons +to prefer type-erasure over traditional object-oriented programming is that with +type-erasure we can implement value semantics, +rather than being forced into pointer/reference semantics. + +There are however situations in which pointer/reference semantics might be preferrable. +For instance, the motivating example for the development of reference semantics in tanuki +was the necessity to support large computational graphs featuring a high degree +of internal repetition in the `heyoka library `__. + +Reference semantics support in tanuki is activated by setting the :cpp:var:`config::semantics` +config option to :cpp:enumerator:`wrap_semantics::reference`. When reference semantics +is activated, the :cpp:class:`wrap` class will employ internally a +`shared pointer `__ to store the type-erased +value, and copy/move/swap operations on a :cpp:class:`wrap` will behave like the corresponding +operations on a shared pointer - that is, they will manipulate references to the internal value, +rather than the value itself. + +Additionally, there are a couple of extra functions in the API that are available only when +using reference semantics. + +The first one, :cpp:func:`wrap::copy()`, is used to make a deep-copy of a :cpp:class:`wrap` +(that is, it enforces the creation of a copy of the internal value, rather than creating +a new shared reference to it). + +The second one, :cpp:func:`wrap::same_value()`, can be used to detect if two :cpp:class:`wrap` +objects share ownership of the same value. diff --git a/doc/wrap.rst b/doc/wrap.rst index b48a1fe..10fe046 100644 --- a/doc/wrap.rst +++ b/doc/wrap.rst @@ -195,6 +195,30 @@ The ``wrap`` class :return: ``true`` if :cpp:var:`w` is currently employing static storage, ``false`` otherwise. + .. cpp:function:: [[nodiscard]] friend wrap copy(const wrap &w) requires(Cfg.semantics == wrap_semantics::reference) + + Make a deep-copy of a :cpp:class:`wrap` employing :ref:`reference semantics `. + + This function will return a new :cpp:class:`wrap` containing a copy of the value stored in :cpp:var:`w`. + + :param w: the input :cpp:class:`wrap`. + + :return: a deep copy of :cpp:var:`w`. + + :throws std\:\:invalid_argument: if the value stored in :cpp:var:`w` is not copy-constructible. + :throws: any exception thrown by memory allocation primitives or by the + copy constructor of the value stored in :cpp:var:`w`. + + .. cpp:function:: [[nodiscard]] friend bool same_value(const wrap &w1, const wrap &w2) noexcept requires(Cfg.semantics == wrap_semantics::reference) + + Check if two :cpp:class:`wrap` objects employing :ref:`reference semantics ` share + ownership of the internal value. + + :param w1: the first :cpp:class:`wrap`. + :param w2: the second :cpp:class:`wrap`. + + :return: ``true`` if :cpp:var:`w1` and :cpp:var:`w2` share the internal value, ``false`` otherwise. + .. cpp:function:: [[nodiscard]] bool is_valid(const wrap &w) noexcept This function will return ``false`` if :cpp:var:`w` is in the :ref:`invalid state `, From 3fd1d5ea02ca46f1d360909fc9f38b9c45f250ce Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 19 May 2024 14:36:00 +0200 Subject: [PATCH 15/15] Tutorial addition. --- doc/ref_semantics.rst | 66 ++++++++++++++++++++++++++++++++++++++ tutorial/CMakeLists.txt | 1 + tutorial/ref_semantics.cpp | 37 +++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 tutorial/ref_semantics.cpp diff --git a/doc/ref_semantics.rst b/doc/ref_semantics.rst index 6685468..7592288 100644 --- a/doc/ref_semantics.rst +++ b/doc/ref_semantics.rst @@ -32,3 +32,69 @@ a new shared reference to it). The second one, :cpp:func:`wrap::same_value()`, can be used to detect if two :cpp:class:`wrap` objects share ownership of the same value. + +Let us see a simple example of reference semantics in action. We begin with our usual, super-simple +interface and its implementation: + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ + :lines: 7-14 + +We introduce a :cpp:class:`wrap` type employing reference semantics: + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ + :lines: 18-18 + +And we create a :cpp:class:`wrap` instance storing a ``std::string``: + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ + :lines: 20-20 + +Let us now make a copy of ``w1``: + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ + :lines: 22-22 + +As explained earlier, this operation will not copy the string inside ``w1``, rather it will create a new reference to it. +We can confirm that this indeed is the case by comparing the addresses of the values stored in ``w1`` and ``w2``: + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ + :lines: 26-27 + +.. code-block:: console + + Address of the value wrapped by w1: 0x606000000038 + Address of the value wrapped by w2: 0x606000000038 + +We can also invoke the :cpp:func:`wrap::same_value()` function for further confirmation: + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ + :lines: 29-29 + +.. code-block:: console + + Do w1 and w2 share ownership? true + +If, on the other hand, we use the :cpp:func:`wrap::copy()` function, we will be performing a copy +of the string stored in ``w1``: + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ + :lines: 31-36 + +.. code-block:: console + + Address of the value wrapped by w1: 0x606000000038 + Address of the value wrapped by w3: 0x606000000098 + Do w1 and w3 share ownership? false + +Full code listing +----------------- + +.. literalinclude:: ../tutorial/ref_semantics.cpp + :language: c++ diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt index 071f028..7439d2f 100644 --- a/tutorial/CMakeLists.txt +++ b/tutorial/CMakeLists.txt @@ -27,4 +27,5 @@ ADD_TANUKI_TUTORIAL(emplace) ADD_TANUKI_TUTORIAL(compose1) ADD_TANUKI_TUTORIAL(compose2) ADD_TANUKI_TUTORIAL(nonintrusive) +ADD_TANUKI_TUTORIAL(ref_semantics) ADD_TANUKI_TUTORIAL(std_function) diff --git a/tutorial/ref_semantics.cpp b/tutorial/ref_semantics.cpp new file mode 100644 index 0000000..7f2ebf7 --- /dev/null +++ b/tutorial/ref_semantics.cpp @@ -0,0 +1,37 @@ +#include +#include +#include + +#include + +template +struct any_iface_impl : public Base { +}; + +struct any_iface { + template + using impl = any_iface_impl; +}; + +int main() +{ + using any_wrap = tanuki::wrap{.semantics = tanuki::wrap_semantics::reference}>; + + any_wrap w1(std::string("hello world")); + + auto w2 = w1; + + std::cout << std::boolalpha; + + std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr(w1) << '\n'; + std::cout << "Address of the value wrapped by w2: " << tanuki::value_ptr(w2) << '\n'; + + std::cout << "Do w1 and w2 share ownership? " << same_value(w1, w2) << '\n'; + + auto w3 = copy(w1); + + std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr(w1) << '\n'; + std::cout << "Address of the value wrapped by w3: " << tanuki::value_ptr(w3) << '\n'; + + std::cout << "Do w1 and w3 share ownership? " << same_value(w1, w3) << '\n'; +}