From fc0d6fd712e470950597b011cba9c3678165dc08 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 10:01:37 +0100 Subject: [PATCH 01/18] Initial code modifications for the updated copy/move rules. --- include/tanuki/tanuki.hpp | 366 ++++++++++++++++++-------------- test/input_iterator.hpp | 3 +- test/io_iterator.hpp | 3 +- test/ranges.hpp | 3 +- test/test_basics.cpp | 17 +- test/test_defctor.cpp | 13 +- test/test_no_copy_move_swap.cpp | 21 +- test/test_nostatic.cpp | 6 +- tutorial/emplace.cpp | 5 +- 9 files changed, 251 insertions(+), 186 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 4198521..34317bb 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -317,23 +317,23 @@ struct TANUKI_VISIBLE _tanuki_value_iface : public IFace { { unreachable(); } - virtual void _tanuki_copy_assign_value_to(_tanuki_value_iface *) const + virtual bool _tanuki_copy_assign_value_to(_tanuki_value_iface *) const { unreachable(); } - virtual void _tanuki_move_assign_value_to(_tanuki_value_iface *) && noexcept + virtual bool _tanuki_move_assign_value_to(_tanuki_value_iface *) && noexcept { unreachable(); } - virtual void _tanuki_copy_assign_value_from(const void *) + virtual bool _tanuki_copy_assign_value_from(const void *) { unreachable(); } - virtual void _tanuki_move_assign_value_from(void *) noexcept + virtual bool _tanuki_move_assign_value_from(void *) noexcept { unreachable(); } - virtual void _tanuki_swap_value(_tanuki_value_iface *) noexcept + virtual bool _tanuki_swap_value(_tanuki_value_iface *) noexcept { unreachable(); } @@ -641,11 +641,9 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) return new _tanuki_holder(_tanuki_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. - unreachable(); // LCOV_EXCL_LINE } // Same as above, but return a shared ptr. [[nodiscard]] std::shared_ptr<_tanuki_value_iface> _tanuki_shared_clone_holder() const final @@ -653,11 +651,6 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface) { return std::make_shared<_tanuki_holder>(_tanuki_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"); } } @@ -672,14 +665,14 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface *_tanuki_move_init_holder(void *ptr) && noexcept final { if constexpr (std::is_move_constructible_v) { @@ -689,17 +682,16 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface *v_iface) const final + // If T is copy-assignable, copy-assign _tanuki_value into the _tanuki_value of v_iface and return true. Otherwise, + // do nothing and return false. + bool _tanuki_copy_assign_value_to(_tanuki_value_iface *v_iface) const final { if constexpr (std::is_copy_assignable_v) { // NOTE: I don't think it is necessary to invoke launder here, as value_ptr() just does a static cast to @@ -708,51 +700,47 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface_tanuki_value_type_index()); *static_cast(v_iface->_tanuki_value_ptr()) = _tanuki_value; - return; + return true; + } else { + return false; } - - // 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. - unreachable(); // LCOV_EXCL_LINE } - // Move-assign _tanuki_value into the _tanuki_value of v_iface. - void _tanuki_move_assign_value_to(_tanuki_value_iface *v_iface) && noexcept final + // If T is move-assignable, move-assign _tanuki_value into the _tanuki_value of v_iface and return true. Otherwise, + // do nothing and return false. + bool _tanuki_move_assign_value_to(_tanuki_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(_tanuki_value); - return; + return true; + } else { + return false; } - - // 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. - unreachable(); // LCOV_EXCL_LINE } - // Copy-assign the object of type T assumed to be stored in ptr into _tanuki_value. - void _tanuki_copy_assign_value_from(const void *ptr) final + // If T is copy-assignable, copy-assign the object of type T assumed to be stored in ptr into _tanuki_value and + // return true. Otherwise, return false. + bool _tanuki_copy_assign_value_from(const void *ptr) final { if constexpr (std::is_copy_assignable_v) { _tanuki_value = *static_cast(ptr); - return; + return true; + } else { + return false; } - - // 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. - unreachable(); // LCOV_EXCL_LINE } - void _tanuki_move_assign_value_from(void *ptr) noexcept final + // If T is move-assignable, move-assign the object of type T assumed to be stored in ptr into _tanuki_value and + // return true. Otherwise, return false. + bool _tanuki_move_assign_value_from(void *ptr) noexcept final { if constexpr (std::is_move_assignable_v) { _tanuki_value = std::move(*static_cast(ptr)); - return; + return true; + } else { + return false; } - - // 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. - unreachable(); // LCOV_EXCL_LINE } - // Swap _tanuki_value with the _tanuki_value of v_iface. - void _tanuki_swap_value(_tanuki_value_iface *v_iface) noexcept final + // If T is swappable, swap _tanuki_value with the _tanuki_value of v_iface and return true. Otherwise, return false. + bool _tanuki_swap_value(_tanuki_value_iface *v_iface) noexcept final { if constexpr (std::swappable) { assert(typeid(T) == v_iface->_tanuki_value_type_index()); @@ -760,12 +748,10 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface(v_iface->_tanuki_value_ptr())); - return; + return true; + } else { + return false; } - - // 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. - unreachable(); // LCOV_EXCL_LINE } #if defined(TANUKI_WITH_BOOST_S11N) @@ -776,7 +762,7 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface; + return std::is_move_constructible_v; } #endif @@ -1008,12 +994,14 @@ struct TANUKI_VISIBLE config final : detail::config_base { wrap_ctor explicit_ctor = wrap_ctor::always_explicit; // Semantics. wrap_semantics semantics = wrap_semantics::value; - // Enable copy construction/assignment. - bool copyable = true; - // Enable move construction/assignment. - bool movable = true; - // Enable swap. - bool swappable = true; + // Enable copy construction. + bool copy_constructible = true; + // Enable copy assignment. + bool copy_assignable = true; + // Enable move construction. + bool move_constructible = true; + // Enable move assignment. + bool move_assignable = true; }; // Default configuration for the wrap class. @@ -1038,10 +1026,10 @@ concept valid_config = (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) && - // 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); + // Copy-assignability requires copy-constructibility. + (!Cfg.copy_assignable || Cfg.copy_constructible) && + // Move-assignability requires move-constructibility. + (!Cfg.move_assignable || Cfg.move_constructible); // Helpers to ease the definition of a reference interface. #define TANUKI_REF_IFACE_MEMFUN(name) \ @@ -1185,13 +1173,6 @@ 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. @@ -1280,7 +1261,7 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface = std::move(*pv_iface)._tanuki_move_init_holder(this->static_storage); // NOTE: when we loaded the serialised pointer, the value contained in the holder was deserialised @@ -1371,17 +1365,17 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface->_tanuki_value_ptr(), pv_iface->_tanuki_value_ptr()); - }(); - - // Clean up pv_iface. - // - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete pv_iface; + // LCOV_EXCL_START + } catch (...) { + // If anything goes wrong, we must first destroy and then set to the invalid state before + // re-throwing. This is all noexcept. + destroy(); + this->m_pv_iface = nullptr; + throw; + } + // LCOV_EXCL_STOP } else { // Assign the deserialised pointer. this->m_pv_iface = pv_iface; @@ -1441,13 +1435,10 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage requires(!Cfg.invalid_default_ctor) && std::default_initializable && - // A default value type must have been specified - // in the configuration. + // A default value type must have been specified in the configuration. (!std::same_as) && // We must be able to construct the holder. - std::constructible_from> && - // Check copy/move/swap consistency. - detail::copy_move_swap_consistent + std::constructible_from> wrap() noexcept(noexcept(this->ctor_impl()) && detail::nothrow_default_initializable) { ctor_impl(); @@ -1463,9 +1454,7 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage, wrap>) && // We must be able to construct the holder. - std::constructible_from>, T &&> && - // Check copy/move/swap consistency. - detail::copy_move_swap_consistent, Cfg> + std::constructible_from>, T &&> 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))) @@ -1478,9 +1467,6 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage requires std::default_initializable && // We must be able to construct the holder. @@ -1502,9 +1488,7 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage && std::same_as> && std::default_initializable && // We must be able to construct the holder. - std::constructible_from, U &&...> && - // Check copy/move consistency. - detail::copy_move_swap_consistent + std::constructible_from, U &&...> explicit wrap(std::in_place_type_t, U &&...args) noexcept(noexcept(this->ctor_impl(std::forward(args)...)) && detail::nothrow_default_initializable) @@ -1512,23 +1496,34 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage(std::forward(args)...); } +private: + void copy_init_from(const wrap &other) noexcept + { + static_assert(Cfg.semantics == wrap_semantics::value); + + if constexpr (Cfg.static_size == 0u) { + // Static storage disabled. + this->m_pv_iface = other.m_pv_iface->_tanuki_clone_holder(); + } else { + if (other.stype()) { + // Other has static storage. + this->m_pv_iface = other.m_pv_iface->_tanuki_copy_init_holder(this->static_storage); + } else { + // Other has dynamic storage. + this->m_pv_iface = other.m_pv_iface->_tanuki_clone_holder(); + } + } + } + +public: + // Copy constructor. 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 + requires(Cfg.copy_constructible || Cfg.semantics == wrap_semantics::reference) + && std::default_initializable { if constexpr (Cfg.semantics == wrap_semantics::value) { - if constexpr (Cfg.static_size == 0u) { - // Static storage disabled. - this->m_pv_iface = other.m_pv_iface->_tanuki_clone_holder(); - } else { - if (other.stype()) { - // Other has static storage. - this->m_pv_iface = other.m_pv_iface->_tanuki_copy_init_holder(this->static_storage); - } else { - // Other has dynamic storage. - this->m_pv_iface = other.m_pv_iface->_tanuki_clone_holder(); - } - } + copy_init_from(other); } else { this->m_pv_iface = other.m_pv_iface; } @@ -1537,11 +1532,11 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface = other.m_pv_iface; // Invalidate other. @@ -1564,7 +1559,8 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage + requires(Cfg.move_constructible || Cfg.semantics == wrap_semantics::reference) + && std::default_initializable { if constexpr (Cfg.semantics == wrap_semantics::value) { move_init_from(std::move(other)); @@ -1575,8 +1571,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface; } else { @@ -1600,7 +1597,7 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface); + // For static storage, directly move assign the internal value, if possible. Otherwise, destroy and + // move-initialise. + // + // NOTE: if move-assigning the internal value throws, the program will terminate. + if (!std::move(*other.m_pv_iface)._tanuki_move_assign_value_to(this->m_pv_iface)) { + destroy_and_move_init(); + } } else { // For dynamic storage, swap the pointer. std::swap(this->m_pv_iface, other.m_pv_iface); @@ -1647,8 +1656,11 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage_tanuki_copy_assign_value_to(this->m_pv_iface)) { + destroy(); - // NOTE: no need to branch on the storage type or static_size here, as everything happens through the value - // interface. - if constexpr (Cfg.static_size > 0u) { - // The storage flags must match, as they depend only - // on the internal types. - assert(stype() == other.stype()); - } + try { + // NOTE: copy_init_from() either succeeds or fails, no intermediate state is possible. + copy_init_from(other); + } catch (...) { + // NOTE: this is important - we want to mark this as invalid before re-throwing. Like this, this + // will be left in the invalid state. + // + // NOTE: we do not need to do this in the move-assignment operator because we do not allow for + // exceptions there. + this->m_pv_iface = nullptr; - // Assign the internal value. - other.m_pv_iface->_tanuki_copy_assign_value_to(this->m_pv_iface); + throw; + } + } } else { this->m_pv_iface = other.m_pv_iface; } @@ -1708,9 +1733,7 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage, wrap>) && // We must be able to construct the holder. - std::constructible_from>, T &&> && - // Check copy/move consistency. - detail::copy_move_swap_consistent, Cfg> + std::constructible_from>, T &&> wrap &operator=(T &&x) { if constexpr (Cfg.semantics == wrap_semantics::value) { @@ -1720,20 +1743,24 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage)) { + // Helper to perform assignment via destruction + initialisation. + const auto destroy_and_init = [this, &x]() { destroy(); try { ctor_impl>(std::forward(x)); } catch (...) { - // NOTE: if ctor_impl fails there's no cleanup required. - // Invalidate this before rethrowing. + // NOTE: this is important - we want to mark this as invalid before re-throwing. Like this, this + // will be left in the invalid state. this->m_pv_iface = nullptr; throw; } + }; + // Handle different types. + if (value_type_index(*this) != typeid(detail::value_t_from_arg)) { + destroy_and_init(); return *this; } @@ -1746,14 +1773,25 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface->_tanuki_copy_assign_value_from(&fptr); + [[maybe_unused]] const auto ret = this->m_pv_iface->_tanuki_copy_assign_value_from(&fptr); + assert(ret); } else { - // The internal types are the same, do directly copy/move assignment. + // The internal types are the same, attempt to directly copy/move assign. + bool ret = false; if constexpr (detail::noncv_rvalue_reference) { - this->m_pv_iface->_tanuki_move_assign_value_from(std::addressof(x)); + ret = this->m_pv_iface->_tanuki_move_assign_value_from(std::addressof(x)); } else { - this->m_pv_iface->_tanuki_copy_assign_value_from(std::addressof(x)); + ret = this->m_pv_iface->_tanuki_copy_assign_value_from(std::addressof(x)); + } + + if (!ret) { + // The internal value does not support copy/move assignment. Resort to destruction + copy/move init. + destroy_and_init(); } } } else { @@ -1778,9 +1816,8 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage && std::same_as> && // We must be able to construct the holder. - std::constructible_from, Args &&...> && + std::constructible_from, Args &&...> // 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) { @@ -1854,6 +1891,7 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage_tanuki_swap_value(w1.m_pv_iface); + // For static storage, attempt to directly swap the internal values. + if (!w2.m_pv_iface->_tanuki_swap_value(w1.m_pv_iface)) { + // The internal value does not support swapping. Resort to the canonical implementation. + canonical_swap(); + } } else { // For dynamic storage, swap the pointers. std::swap(w1.m_pv_iface, w2.m_pv_iface); diff --git a/test/input_iterator.hpp b/test/input_iterator.hpp index fd792cd..815ac33 100644 --- a/test/input_iterator.hpp +++ b/test/input_iterator.hpp @@ -111,7 +111,8 @@ inline constexpr auto input_iterator_config = tanuki::config, input_iterator_iface>, .static_align = tanuki::holder_align, input_iterator_iface>, .pointer_interface = false, - .copyable = false}; + .copy_constructible = false, + .copy_assignable = false}; } // namespace detail diff --git a/test/io_iterator.hpp b/test/io_iterator.hpp index 4a73a4c..3e23103 100644 --- a/test/io_iterator.hpp +++ b/test/io_iterator.hpp @@ -136,7 +136,8 @@ inline constexpr auto io_iterator_config = tanuki::config, io_iterator_iface>, .static_align = tanuki::holder_align, io_iterator_iface>, .pointer_interface = false, - .copyable = false}; + .copy_constructible = false, + .copy_assignable = false}; } // namespace detail diff --git a/test/ranges.hpp b/test/ranges.hpp index 0a42fbf..527ebac 100644 --- a/test/ranges.hpp +++ b/test/ranges.hpp @@ -311,7 +311,8 @@ inline constexpr auto generic_range_config = tanuki::config, generic_range_iface>, .pointer_interface = false, - .copyable = false}; + .copy_constructible = false, + .copy_assignable = false}; template typename It> diff --git a/test/test_basics.cpp b/test/test_basics.cpp index eb1d0fe..89474ab 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -198,8 +198,11 @@ TEST_CASE("basics") REQUIRE(has_dynamic_storage(w2)); // Emplace test with class which is not copyable/movable/swappable. - wrap{.copyable = false, .movable = false, .swappable = false}> w_mut( - std::in_place_type); + wrap{.copy_constructible = false, + .copy_assignable = false, + .move_constructible = false, + .move_assignable = false}> + w_mut(std::in_place_type); REQUIRE(noexcept(wrap_t(std::in_place_type))); // Check throwing in value_ref. @@ -513,7 +516,10 @@ TEST_CASE("s11n nodef") using Catch::Matchers::MessageMatches; using Catch::Matchers::StartsWith; - using wrap_t = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; + using wrap_t = tanuki::wrap{.copy_constructible = false, + .copy_assignable = false, + .move_constructible = false, + .move_assignable = false}>; const wrap_t w(nodefctor{3}); @@ -540,7 +546,10 @@ TEST_CASE("s11n nomove") using Catch::Matchers::MessageMatches; using Catch::Matchers::StartsWith; - using wrap_t = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; + using wrap_t = tanuki::wrap{.copy_constructible = false, + .copy_assignable = false, + .move_constructible = false, + .move_assignable = false}>; const wrap_t w(std::in_place_type); diff --git a/test/test_defctor.cpp b/test/test_defctor.cpp index 78b31f0..8f20b94 100644 --- a/test/test_defctor.cpp +++ b/test/test_defctor.cpp @@ -91,17 +91,14 @@ TEST_CASE("def value type") 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}>; + using wrap6_t = tanuki::wrap{.copy_constructible = false, .copy_assignable = false}>; REQUIRE(std::default_initializable); - using wrap7_t = tanuki::wrap{.copyable = false, .movable = false}>; + using wrap7_t = tanuki::wrap{.copy_constructible = false, + .copy_assignable = false, + .move_constructible = false, + .move_assignable = false}>; REQUIRE(std::default_initializable); } diff --git a/test/test_no_copy_move_swap.cpp b/test/test_no_copy_move_swap.cpp index a62c296..bfd7bb9 100644 --- a/test/test_no_copy_move_swap.cpp +++ b/test/test_no_copy_move_swap.cpp @@ -42,14 +42,14 @@ TEST_CASE("noncopyable") { using wrap_t = tanuki::wrap; - REQUIRE(!std::constructible_from); - REQUIRE(!std::constructible_from>); - REQUIRE(!emplaceable); + 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}>); + REQUIRE(!tanuki::valid_config{.move_constructible = false, .move_assignable = true}>); + REQUIRE(!tanuki::valid_config{.copy_constructible = false, .copy_assignable = true}>); - using wrap2_t = tanuki::wrap{.copyable = false}>; + using wrap2_t = tanuki::wrap{.copy_constructible = false, .copy_assignable = false}>; REQUIRE(std::constructible_from); REQUIRE(std::constructible_from>); @@ -84,10 +84,13 @@ TEST_CASE("nonmovable") using wrap_t = tanuki::wrap; REQUIRE(!std::constructible_from); - REQUIRE(!std::constructible_from>); - REQUIRE(!emplaceable); + REQUIRE(std::constructible_from>); + REQUIRE(emplaceable); - using wrap2_t = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; + using wrap2_t = tanuki::wrap{.copy_constructible = false, + .copy_assignable = false, + .move_constructible = false, + .move_assignable = false}>; REQUIRE(!std::constructible_from); REQUIRE(std::constructible_from>); diff --git a/test/test_nostatic.cpp b/test/test_nostatic.cpp index b67b723..5dc987d 100644 --- a/test/test_nostatic.cpp +++ b/test/test_nostatic.cpp @@ -130,7 +130,11 @@ TEST_CASE("basics") // NOLINTEND // Emplace test with class which is not copyable/movable. - tanuki::wrap{.static_size = 0, .copyable = false, .movable = false, .swappable = false}> + tanuki::wrap{.static_size = 0, + .copy_constructible = false, + .copy_assignable = false, + .move_constructible = false, + .move_assignable = false}> w_mut(std::in_place_type); REQUIRE(!noexcept(wrap_t(std::in_place_type))); diff --git a/tutorial/emplace.cpp b/tutorial/emplace.cpp index 5698acd..2eaf060 100644 --- a/tutorial/emplace.cpp +++ b/tutorial/emplace.cpp @@ -14,7 +14,10 @@ struct any_iface { int main() { - using any_wrap = tanuki::wrap{.copyable = false, .movable = false, .swappable = false}>; + using any_wrap = tanuki::wrap{.copy_constructible = false, + .copy_assignable = false, + .move_constructible = false, + .move_assignable = false}>; // Emplace-construct with std::mutex. any_wrap w(std::in_place_type); From 1dc2e1da888045dcc8891f6b9a0bd5f641c53cf7 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 10:04:45 +0100 Subject: [PATCH 02/18] Fix macos gha setup. --- .github/workflows/gha.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gha.yml b/.github/workflows/gha.yml index e62aa3d..e112fe4 100644 --- a/.github/workflows/gha.yml +++ b/.github/workflows/gha.yml @@ -40,13 +40,13 @@ jobs: - name: Build run: bash tools/gha_conda_docs.sh osx_clang_16: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Build run: bash tools/gha_osx_clang_16.sh osx_clang_18: - runs-on: macos-13 + runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Build From d2b8ca529ebe7ddc14125a0f6652f73dfea8a76a Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 11:11:53 +0100 Subject: [PATCH 03/18] tidy fixes. --- .clang-tidy | 2 +- test/CMakeLists.txt | 2 +- test/input_iterator.cpp | 7 +++++-- test/io_iterator.cpp | 24 ++++++++++++++++++++++-- test/{ => sqlite}/sqlite3.c | 0 test/{ => sqlite}/sqlite3.h | 0 test/test_basics.cpp | 2 ++ test/test_gen_assignment.cpp | 2 ++ tutorial/compose1.cpp | 4 ++++ tutorial/compose2.cpp | 8 ++++++-- tutorial/custom_storage.cpp | 6 +++--- tutorial/hello_world.cpp | 11 ++++++----- tutorial/nonintrusive.cpp | 7 ++++--- tutorial/reference_interface.cpp | 5 ++++- tutorial/simple_interface.cpp | 3 +++ tutorial/std_function.cpp | 8 ++++++-- tutorial/wrap_reference.cpp | 5 ++++- 17 files changed, 73 insertions(+), 23 deletions(-) rename test/{ => sqlite}/sqlite3.c (100%) rename test/{ => sqlite}/sqlite3.h (100%) diff --git a/.clang-tidy b/.clang-tidy index 57ecc8f..d751ee6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,*,-hicpp-uppercase-literal-suffix,-readability-uppercase-literal-suffix,-modernize-use-trailing-return-type,-readability-named-parameter,-hicpp-named-parameter,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-header-guard,-cppcoreguidelines-macro-usage,-google-runtime-references,-readability-isolate-declaration,-fuchsia-default-arguments-calls,-fuchsia-overloaded-operator,-fuchsia-default-arguments-declarations,-readability-else-after-return,-google-runtime-int,-hicpp-signed-bitwise,-cert-dcl21-cpp,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-avoid-c-arrays,-hicpp-avoid-c-arrays,-modernize-avoid-c-arrays,-modernize-use-transparent-functors,-cert-dcl16-c,-cppcoreguidelines-pro-type-union-access,-bugprone-branch-clone,-fuchsia-statically-constructed-objects,-cppcoreguidelines-pro-bounds-constant-array-index,-readability-static-accessed-through-instance,-cppcoreguidelines-pro-type-vararg,-hicpp-vararg,-llvmlibc-restrict-system-libc-headers,-llvmlibc-callee-namespace,-llvmlibc-implementation-in-namespace,-llvm-else-after-return,-fuchsia-trailing-return,-readability-identifier-length,-altera-unroll-loops,-altera-id-dependent-backward-branch,-altera-struct-pack-align,-performance-no-int-to-ptr,-readability-function-cognitive-complexity,-llvmlibc-inline-function-decl,-misc-include-cleaner,-fuchsia-multiple-inheritance' +Checks: 'clang-diagnostic-*,clang-analyzer-*,*,-hicpp-uppercase-literal-suffix,-readability-uppercase-literal-suffix,-modernize-use-trailing-return-type,-readability-named-parameter,-hicpp-named-parameter,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-header-guard,-cppcoreguidelines-macro-usage,-google-runtime-references,-readability-isolate-declaration,-fuchsia-default-arguments-calls,-fuchsia-overloaded-operator,-fuchsia-default-arguments-declarations,-readability-else-after-return,-google-runtime-int,-hicpp-signed-bitwise,-cert-dcl21-cpp,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-avoid-c-arrays,-hicpp-avoid-c-arrays,-modernize-avoid-c-arrays,-modernize-use-transparent-functors,-cert-dcl16-c,-cppcoreguidelines-pro-type-union-access,-bugprone-branch-clone,-fuchsia-statically-constructed-objects,-cppcoreguidelines-pro-bounds-constant-array-index,-readability-static-accessed-through-instance,-cppcoreguidelines-pro-type-vararg,-hicpp-vararg,-llvmlibc-restrict-system-libc-headers,-llvmlibc-callee-namespace,-llvmlibc-implementation-in-namespace,-llvm-else-after-return,-fuchsia-trailing-return,-readability-identifier-length,-altera-unroll-loops,-altera-id-dependent-backward-branch,-altera-struct-pack-align,-performance-no-int-to-ptr,-readability-function-cognitive-complexity,-llvmlibc-inline-function-decl,-misc-include-cleaner,-fuchsia-multiple-inheritance,-readability-use-concise-preprocessor-directives,-portability-template-virtual-member-function' WarningsAsErrors: '*' FormatStyle: none User: yardbird diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index eb415ed..80fe114 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,7 +29,7 @@ endfunction() set(THREADS_PREFER_PTHREAD_FLAG YES) find_package(Threads REQUIRED) unset(THREADS_PREFER_PTHREAD_FLAG) -add_library(tanuki_sqlite STATIC sqlite3.c) +add_library(tanuki_sqlite STATIC sqlite/sqlite3.c) target_link_libraries(tanuki_sqlite PRIVATE ${CMAKE_DL_LIBS} Threads::Threads) ADD_TANUKI_TESTCASE(test_basics) diff --git a/test/input_iterator.cpp b/test/input_iterator.cpp index 94ab1a2..5f9e8a1 100644 --- a/test/input_iterator.cpp +++ b/test/input_iterator.cpp @@ -10,7 +10,10 @@ #include "input_iterator.hpp" #include "sentinel.hpp" -// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) +// NOTE: the ArrayBound nolint is necessary because clang-tidy apparently gets confused in the test involving a +// statically-allocated array. +// +// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,clang-analyzer-security.ArrayBound) template concept can_make_input_iterator = requires(T it) { facade::make_input_iterator(it); }; @@ -226,4 +229,4 @@ TEST_CASE("iter_move") REQUIRE(ns::iter_move1_counter == 3); } -// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) +// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,clang-analyzer-security.ArrayBound) diff --git a/test/io_iterator.cpp b/test/io_iterator.cpp index 3d6eeee..6d13b7f 100644 --- a/test/io_iterator.cpp +++ b/test/io_iterator.cpp @@ -11,7 +11,10 @@ #include "io_iterator.hpp" #include "sentinel.hpp" -// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) +// NOTE: the ArrayBound nolint is necessary because clang-tidy apparently gets confused in the test involving a +// statically-allocated array. +// +// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,clang-analyzer-security.ArrayBound) template concept can_make_io_iterator = requires(T it) { facade::make_io_iterator(it); }; @@ -36,9 +39,12 @@ TEST_CASE("basic") int_iter it(std::begin(arr)); REQUIRE(has_static_storage(it)); REQUIRE(*it == 1); + REQUIRE(&*it == arr); REQUIRE(*++it == 2); + REQUIRE(&*it == arr + 1); it++; REQUIRE(*it == 3); + REQUIRE(&*it == arr + 2); // Check that make_io_iterator() on an io_iterator // returns a copy. @@ -58,6 +64,20 @@ TEST_CASE("basic") REQUIRE(std::sentinel_for); } + { + std::vector vec = {1, 2, 3}; + auto it = facade::make_io_iterator(vec.data()); + REQUIRE(std::same_as); + REQUIRE(*it == 1); + REQUIRE(&*it == vec.data()); + REQUIRE(*++it == 2); + REQUIRE(&*it == vec.data() + 1); + it++; + REQUIRE(*it == 3); + REQUIRE(&*it == vec.data() + 2); + REQUIRE(std::sentinel_for); + } + { // NOLINTNEXTLINE(misc-const-correctness) std::vector vec = {1, 2, 3}; @@ -118,4 +138,4 @@ TEST_CASE("noniter") REQUIRE(std::same_as); } -// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) +// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,clang-analyzer-security.ArrayBound) diff --git a/test/sqlite3.c b/test/sqlite/sqlite3.c similarity index 100% rename from test/sqlite3.c rename to test/sqlite/sqlite3.c diff --git a/test/sqlite3.h b/test/sqlite/sqlite3.h similarity index 100% rename from test/sqlite3.h rename to test/sqlite/sqlite3.h diff --git a/test/test_basics.cpp b/test/test_basics.cpp index 89474ab..f1aaede 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -109,6 +109,7 @@ TANUKI_S11N_WRAP_EXPORT2(small2, "small2", any_iface) namespace { +// NOLINTNEXTLINE void my_func(int) {} } // namespace @@ -261,6 +262,7 @@ TEST_CASE("assignment") wrap_t w(42); // Self assign, copy and move. + // NOLINTNEXTLINE REQUIRE_NOTHROW(w = *&w); // NOLINTNEXTLINE REQUIRE_NOTHROW(w = std::move(*&w)); diff --git a/test/test_gen_assignment.cpp b/test/test_gen_assignment.cpp index 5fe2ec3..8b8bb40 100644 --- a/test/test_gen_assignment.cpp +++ b/test/test_gen_assignment.cpp @@ -56,6 +56,8 @@ struct cm_throw { { throw std::invalid_argument("copy assignment"); } + cm_throw &operator=(cm_throw &&) noexcept = default; + friend void swap(cm_throw &, cm_throw &) noexcept {} }; static void my_func1(int) {} diff --git a/tutorial/compose1.cpp b/tutorial/compose1.cpp index 774ce5e..02d90f8 100644 --- a/tutorial/compose1.cpp +++ b/tutorial/compose1.cpp @@ -11,6 +11,7 @@ struct foo_iface_impl : public Base { }; // The foo() interface. +// NOLINTNEXTLINE struct foo_iface { virtual void foo() const = 0; @@ -27,6 +28,7 @@ struct bar_iface_impl : public Base { }; // The bar() interface. +// NOLINTNEXTLINE struct bar_iface { virtual void bar() const = 0; @@ -35,10 +37,12 @@ struct bar_iface { }; struct foobar_model { + // NOLINTNEXTLINE void foo() const { std::cout << "Invoking foobar_model::foo()" << '\n'; } + // NOLINTNEXTLINE void bar() const { std::cout << "Invoking foobar_model::bar()" << '\n'; diff --git a/tutorial/compose2.cpp b/tutorial/compose2.cpp index 6281e73..7125694 100644 --- a/tutorial/compose2.cpp +++ b/tutorial/compose2.cpp @@ -11,6 +11,7 @@ struct foo_iface_impl : public Base { }; // The foo() interface. +// NOLINTNEXTLINE struct foo_iface { virtual void foo() const = 0; @@ -27,6 +28,7 @@ struct bar_iface_impl : foo_iface_impl { }; // The bar() interface. +// NOLINTNEXTLINE struct bar_iface : foo_iface { virtual void bar() const = 0; @@ -35,14 +37,16 @@ struct bar_iface : foo_iface { }; struct foobar_model { + // NOLINTNEXTLINE void foo() const { std::cout << "Invoking foobar_model::foo()" << '\n'; - }; + } + // NOLINTNEXTLINE void bar() const { std::cout << "Invoking foobar_model::bar()" << '\n'; - }; + } }; int main() diff --git a/tutorial/custom_storage.cpp b/tutorial/custom_storage.cpp index dc5b691..45b8f1f 100644 --- a/tutorial/custom_storage.cpp +++ b/tutorial/custom_storage.cpp @@ -17,7 +17,7 @@ int main() { using wrap1_t = tanuki::wrap; - wrap1_t w1(nullptr); + const wrap1_t w1(nullptr); std::cout << std::boolalpha; std::cout << "Is w1 using static storage? " << has_static_storage(w1) << '\n'; @@ -27,7 +27,7 @@ int main() void *p2 = nullptr; }; - wrap1_t w2(two_ptrs{}); + const wrap1_t w2(two_ptrs{}); std::cout << "Is w2 using static storage? " << has_static_storage(w2) << '\n'; @@ -36,7 +36,7 @@ int main() using wrap2_t = tanuki::wrap; - wrap2_t w3(nullptr); + const wrap2_t w3(nullptr); std::cout << "Is w3 using static storage? " << has_static_storage(w3) << '\n'; diff --git a/tutorial/hello_world.cpp b/tutorial/hello_world.cpp index 7826e6f..962dbd8 100644 --- a/tutorial/hello_world.cpp +++ b/tutorial/hello_world.cpp @@ -8,6 +8,7 @@ template struct any_iface_impl : public Base { }; +// NOLINTNEXTLINE struct any_iface { virtual ~any_iface() = default; @@ -29,20 +30,20 @@ struct any2 : any_iface { int main() { // Traditional OO-style. - std::unique_ptr ptr1 = std::make_unique(); - std::unique_ptr ptr2 = std::make_unique("hello world"); + const std::unique_ptr ptr1 = std::make_unique(); + const std::unique_ptr ptr2 = std::make_unique("hello world"); // Type-erasure approach. using any_wrap = tanuki::wrap; // Store an integer. - any_wrap w1(42); + const any_wrap w1(42); // Store a string. - any_wrap w2(std::string("hello world")); + const any_wrap w2(std::string("hello world")); // Store anything... struct foo { }; - any_wrap w3(foo{}); + const any_wrap w3(foo{}); } diff --git a/tutorial/nonintrusive.cpp b/tutorial/nonintrusive.cpp index 1d284ac..18229fd 100644 --- a/tutorial/nonintrusive.cpp +++ b/tutorial/nonintrusive.cpp @@ -7,9 +7,10 @@ namespace ns { // An existing OO interface. +// NOLINTNEXTLINE struct my_iface { virtual ~my_iface() = default; - virtual int foo() const = 0; + [[nodiscard]] virtual int foo() const = 0; }; } // namespace ns @@ -20,7 +21,7 @@ namespace tanuki // Non-intrusive implementation for the ns::my_iface interface. template struct iface_impl : public Base { - int foo() const override + [[nodiscard]] int foo() const override { return 42; } @@ -34,7 +35,7 @@ int main() using wrap_t = tanuki::wrap; wrap_t w1(123); - wrap_t w2(std::string("hello world!")); + const wrap_t w2(std::string("hello world!")); std::cout << "The final answer is " << w1->foo() << '\n'; } diff --git a/tutorial/reference_interface.cpp b/tutorial/reference_interface.cpp index 8486e31..c28cc60 100644 --- a/tutorial/reference_interface.cpp +++ b/tutorial/reference_interface.cpp @@ -11,6 +11,7 @@ struct foo_iface_impl : public Base { } }; +// NOLINTNEXTLINE struct foo_iface { virtual void foo() const = 0; @@ -24,6 +25,7 @@ struct foo_model { struct foo_ref_iface1 { template + // NOLINTNEXTLINE struct impl { TANUKI_REF_IFACE_MEMFUN(foo) }; @@ -31,6 +33,7 @@ struct foo_ref_iface1 { struct foo_ref_iface2 { template + // NOLINTNEXTLINE struct impl { void foo() const { @@ -72,7 +75,7 @@ int main() using foo_wrap2 = tanuki::wrap; - foo_wrap2 w2(foo_model{}); + const foo_wrap2 w2(foo_model{}); w2.foo(); #if defined(TANUKI_HAVE_EXPLICIT_THIS) diff --git a/tutorial/simple_interface.cpp b/tutorial/simple_interface.cpp index 9aa98a8..0d57dd0 100644 --- a/tutorial/simple_interface.cpp +++ b/tutorial/simple_interface.cpp @@ -3,6 +3,7 @@ #include struct foo_model { + // NOLINTNEXTLINE void foo() const { std::cout << "foo_model calling foo()\n"; @@ -22,6 +23,7 @@ struct foo3_iface_impl : public Base { } }; +// NOLINTNEXTLINE struct foo3_iface { virtual void foo() const = 0; @@ -51,6 +53,7 @@ struct foo4_iface_impl : public Base { } }; +// NOLINTNEXTLINE struct foo4_iface { virtual void foo() const = 0; diff --git a/tutorial/std_function.cpp b/tutorial/std_function.cpp index 6e16da0..676f76b 100644 --- a/tutorial/std_function.cpp +++ b/tutorial/std_function.cpp @@ -25,6 +25,7 @@ struct callable_iface_impl { }; template +// NOLINTNEXTLINE struct callable_iface { virtual R operator()(Args... args) const = 0; virtual explicit operator bool() const noexcept = 0; @@ -130,11 +131,13 @@ template requires(requires() { typename callable_impl::type; }) using callable = typename callable_impl::type; +// NOLINTNEXTLINE int doubler(int n) { return n * 2; } +// NOLINTNEXTLINE int main() { assert(!callable{}); @@ -143,7 +146,7 @@ int main() assert(!callable{callable{}}); auto lambda_double = [](int n) { return n * 2; }; - callable c0 = lambda_double; + const callable c0 = lambda_double; assert(c0(2) == 4); callable c0_ref = std::ref(lambda_double); @@ -154,8 +157,9 @@ int main() m = m + n; return n * 2; }; + // NOLINTNEXTLINE assert((!std::constructible_from, decltype(mutable_lambda)>)); - callable c1 = doubler; + const callable c1 = doubler; assert(c1(2) == 4); } diff --git a/tutorial/wrap_reference.cpp b/tutorial/wrap_reference.cpp index 8d7d3fc..25dfe3b 100644 --- a/tutorial/wrap_reference.cpp +++ b/tutorial/wrap_reference.cpp @@ -28,6 +28,7 @@ struct foobar_iface_impl : public Base { } }; +// NOLINTNEXTLINE struct foobar_iface { virtual void foo() const = 0; virtual void bar() = 0; @@ -37,10 +38,12 @@ struct foobar_iface { }; struct foobar_model { + // NOLINTNEXTLINE void foo() const { std::cout << "foobar_model calling foo()\n"; } + // NOLINTNEXTLINE void bar() { std::cout << "foobar_model calling bar()\n"; @@ -65,7 +68,7 @@ int main() std::cout << "The value in w2 points to: " << &value_ref>(w2).get() << '\n'; // Store a const reference to f. - wrap_t w3{std::cref(f)}; + const wrap_t w3{std::cref(f)}; // WARNING: this will throw an exception! // w3->bar(); From 2917e294e9b9d075454a13769442c152165c9058 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 11:12:07 +0100 Subject: [PATCH 04/18] Ignore sqlite in clang-tidy. --- test/sqlite/.clang-tidy | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 test/sqlite/.clang-tidy diff --git a/test/sqlite/.clang-tidy b/test/sqlite/.clang-tidy new file mode 100644 index 0000000..40d63b0 --- /dev/null +++ b/test/sqlite/.clang-tidy @@ -0,0 +1,5 @@ +# Disable all checks in this folder. +# NOTE: the 'llvm-twine-local' is sort of a dummy check because +# clang-tidy does not allow to have all checks disabled. See: +# https://gitlab.kitware.com/cmake/cmake/-/merge_requests/777/diffs +Checks: '-*,llvm-twine-local' From 8d9002af6faa4ac3e332181907ec269c164e54cf Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 11:16:12 +0100 Subject: [PATCH 05/18] Fix include path. --- test/sqlite_ts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sqlite_ts.cpp b/test/sqlite_ts.cpp index c658695..3f8fb68 100644 --- a/test/sqlite_ts.cpp +++ b/test/sqlite_ts.cpp @@ -9,7 +9,7 @@ #include -#include "sqlite3.h" +#include "sqlite/sqlite3.h" #include "time_series.hpp" using sqlite_val_t = std::variant, std::nullptr_t>; From fd68ce38e087537dcb97b19ea22d4950ecb29343 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 11:26:44 +0100 Subject: [PATCH 06/18] Try fixing the osx build. --- .github/workflows/gha.yml | 10 ++------ tools/{gha_osx_clang_18.sh => gha_osx.sh} | 2 +- tools/gha_osx_clang_16.sh | 31 ----------------------- 3 files changed, 3 insertions(+), 40 deletions(-) rename tools/{gha_osx_clang_18.sh => gha_osx.sh} (89%) delete mode 100755 tools/gha_osx_clang_16.sh diff --git a/.github/workflows/gha.yml b/.github/workflows/gha.yml index e112fe4..b81b464 100644 --- a/.github/workflows/gha.yml +++ b/.github/workflows/gha.yml @@ -39,18 +39,12 @@ jobs: - uses: actions/checkout@v4 - name: Build run: bash tools/gha_conda_docs.sh - osx_clang_16: + osx: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Build - run: bash tools/gha_osx_clang_16.sh - osx_clang_18: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Build - run: bash tools/gha_osx_clang_18.sh + run: bash tools/gha_osx.sh windows_vs2022_cpp20: runs-on: windows-2022 steps: diff --git a/tools/gha_osx_clang_18.sh b/tools/gha_osx.sh similarity index 89% rename from tools/gha_osx_clang_18.sh rename to tools/gha_osx.sh index b036d7d..cf8cd21 100755 --- a/tools/gha_osx_clang_18.sh +++ b/tools/gha_osx.sh @@ -11,7 +11,7 @@ wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge export deps_dir=$HOME/local export PATH="$HOME/miniconda/bin:$PATH" bash miniconda.sh -b -p $HOME/miniconda -mamba create -y -q -p $deps_dir 'clangxx=18.*' cmake libboost-devel ninja +mamba create -y -q -p $deps_dir c-compiler cxx-compiler cmake libboost-devel ninja source activate $deps_dir # Create the build dir and cd into it. diff --git a/tools/gha_osx_clang_16.sh b/tools/gha_osx_clang_16.sh deleted file mode 100755 index 45c67b6..0000000 --- a/tools/gha_osx_clang_16.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -# Echo each command -set -x - -# Exit on error. -set -e - -# Install conda+deps. -wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-x86_64.sh -O miniconda.sh -export deps_dir=$HOME/local -export PATH="$HOME/miniconda/bin:$PATH" -bash miniconda.sh -b -p $HOME/miniconda -mamba create -y -q -p $deps_dir 'clangxx=16.*' cmake libboost-devel ninja -source activate $deps_dir - -# Create the build dir and cd into it. -mkdir build -cd build - -cmake -G Ninja ../ -DCMAKE_PREFIX_PATH=$deps_dir \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_BUILD_TYPE=Debug \ - -DTANUKI_BUILD_TESTS=yes \ - -DTANUKI_BUILD_TUTORIALS=yes \ - -DTANUKI_WITH_BOOST_S11N=yes -ninja -ctest -V -j4 - -set +e -set +x From 395c8aaad31e58eedeb15a8f4a435e29058acb40 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 11:40:48 +0100 Subject: [PATCH 07/18] Attempt visibility fix for osx. --- include/tanuki/tanuki.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 34317bb..e6837ab 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -420,7 +420,7 @@ struct iface_impl_base { // NOTE: prohibit the definition of an external implementation for the composite interface. template requires(!detail::is_composite_interface_v) -struct iface_impl final : detail::iface_impl_base { +struct TANUKI_VISIBLE iface_impl final : detail::iface_impl_base { }; namespace detail @@ -475,7 +475,7 @@ struct impl_from_iface_impl { // which in turn allows us - in the implementation of getval() - to static_cast a pointer to an implementation to the // holder for that implementation. We would not be able to perform this static_cast without the information about T. template -struct _tanuki_typed_value_iface : _tanuki_value_iface { +struct TANUKI_VISIBLE _tanuki_typed_value_iface : _tanuki_value_iface { }; // For non-composite interfaces, the Base for the interface implementation is _tanuki_typed_value_iface From f87f13d05386239cdbb11e2e76f6f1198f8d84f2 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 12:03:06 +0100 Subject: [PATCH 08/18] More. --- include/tanuki/tanuki.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index e6837ab..8700208 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -932,7 +932,7 @@ struct TANUKI_VISIBLE composite_ref_iface { // Enum to select the explicitness of the generic wrap ctors. // // NOLINTNEXTLINE(performance-enum-size) -enum class wrap_ctor { always_explicit, ref_implicit, always_implicit }; +enum class TANUKI_VISIBLE wrap_ctor { always_explicit, ref_implicit, always_implicit }; namespace detail { @@ -1101,7 +1101,7 @@ inline constexpr bool is_in_place_type_v> = true; // Implementation of the pointer interface for the wrap class, conditionally-enabled depending on the configuration. template -struct wrap_pointer_iface { +struct TANUKI_VISIBLE wrap_pointer_iface { const IFace *operator->() const noexcept { return iface_ptr(*static_cast(this)); @@ -1122,7 +1122,7 @@ struct wrap_pointer_iface { }; template -struct wrap_pointer_iface { +struct TANUKI_VISIBLE wrap_pointer_iface { }; } // namespace detail From 26a73084051f77b1d444bf8eabf382fa50003994 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 12:36:14 +0100 Subject: [PATCH 09/18] Debug OSX. --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 80fe114..1ec9e7e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -68,8 +68,8 @@ ADD_TANUKI_TESTCASE(test_subrange) if(TANUKI_WITH_BOOST_S11N) add_library(fooable SHARED fooable.cpp) - set_target_properties(fooable PROPERTIES CXX_VISIBILITY_PRESET hidden) - set_target_properties(fooable PROPERTIES VISIBILITY_INLINES_HIDDEN TRUE) + # set_target_properties(fooable PROPERTIES CXX_VISIBILITY_PRESET hidden) + # set_target_properties(fooable PROPERTIES VISIBILITY_INLINES_HIDDEN TRUE) target_compile_options(fooable PRIVATE "$<$:${TANUKI_CXX_FLAGS_DEBUG}>" "$<$:${TANUKI_CXX_FLAGS_RELEASE}>" From 415a863c94135d00e89724015f54d28abf46e31e Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 12:51:16 +0100 Subject: [PATCH 10/18] Revert "Debug OSX." This reverts commit 26a73084051f77b1d444bf8eabf382fa50003994. --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1ec9e7e..80fe114 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -68,8 +68,8 @@ ADD_TANUKI_TESTCASE(test_subrange) if(TANUKI_WITH_BOOST_S11N) add_library(fooable SHARED fooable.cpp) - # set_target_properties(fooable PROPERTIES CXX_VISIBILITY_PRESET hidden) - # set_target_properties(fooable PROPERTIES VISIBILITY_INLINES_HIDDEN TRUE) + set_target_properties(fooable PROPERTIES CXX_VISIBILITY_PRESET hidden) + set_target_properties(fooable PROPERTIES VISIBILITY_INLINES_HIDDEN TRUE) target_compile_options(fooable PRIVATE "$<$:${TANUKI_CXX_FLAGS_DEBUG}>" "$<$:${TANUKI_CXX_FLAGS_RELEASE}>" From 1457565644ca65a840d465d43097e8985ae3ab13 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 12:56:40 +0100 Subject: [PATCH 11/18] More debugging. --- test/test_lib_s11n.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_lib_s11n.cpp b/test/test_lib_s11n.cpp index ddf6836..62b3627 100644 --- a/test/test_lib_s11n.cpp +++ b/test/test_lib_s11n.cpp @@ -1,9 +1,11 @@ #include +#include #include #include #include "fooable.hpp" +#include "tanuki/tanuki.hpp" // NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) @@ -13,6 +15,8 @@ TEST_CASE("test s11n") { fooable::foo_wrap f{fooable::foo_model{.n = 5}}; + std::cout << "Constructed!" << std::endl; + std::stringstream ss; { @@ -20,6 +24,8 @@ TEST_CASE("test s11n") oa << f; } + std::cout << "Serialised!" << std::endl; + value_ref(f).n = 0; { @@ -27,6 +33,9 @@ TEST_CASE("test s11n") ia >> f; } + std::cout << "Deserialised!" << std::endl; + std::cout << "The internal value type is: " << tanuki::demangle(value_type_index(f).name()) << std::endl; + REQUIRE(value_ref(f).n == 5); } From fe9a63e95f551b9f854ae66c7346ee33f9651d20 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 14:34:13 +0100 Subject: [PATCH 12/18] Revert "More debugging." This reverts commit 1457565644ca65a840d465d43097e8985ae3ab13. --- test/test_lib_s11n.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/test_lib_s11n.cpp b/test/test_lib_s11n.cpp index 62b3627..ddf6836 100644 --- a/test/test_lib_s11n.cpp +++ b/test/test_lib_s11n.cpp @@ -1,11 +1,9 @@ #include -#include #include #include #include "fooable.hpp" -#include "tanuki/tanuki.hpp" // NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) @@ -15,8 +13,6 @@ TEST_CASE("test s11n") { fooable::foo_wrap f{fooable::foo_model{.n = 5}}; - std::cout << "Constructed!" << std::endl; - std::stringstream ss; { @@ -24,8 +20,6 @@ TEST_CASE("test s11n") oa << f; } - std::cout << "Serialised!" << std::endl; - value_ref(f).n = 0; { @@ -33,9 +27,6 @@ TEST_CASE("test s11n") ia >> f; } - std::cout << "Deserialised!" << std::endl; - std::cout << "The internal value type is: " << tanuki::demangle(value_type_index(f).name()) << std::endl; - REQUIRE(value_ref(f).n == 5); } From a3d27f8ec3ff1a79ac2f9ce1ad695ad6a49364d1 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Fri, 19 Dec 2025 16:31:59 +0100 Subject: [PATCH 13/18] Squashed 'cmake/yacma/' changes from bd69e66..29c562d 29c562d Small internal doc bit. 57b962f A test without strict preproc on MSVC. 4d54db4 Move strict conformance mode setup flags for MSVC into yacma. f29ab7c Enable the -fno-assume-unique-vtables flag is possible on clang, in order to work around likely buggy compiler optimisation. git-subtree-dir: cmake/yacma git-subtree-split: 29c562d779684c38f31df35b376bdd6281209845 --- YACMACompilerLinkerSettings.cmake | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/YACMACompilerLinkerSettings.cmake b/YACMACompilerLinkerSettings.cmake index 3d9cd9d..5324d71 100644 --- a/YACMACompilerLinkerSettings.cmake +++ b/YACMACompilerLinkerSettings.cmake @@ -125,6 +125,18 @@ if(NOT _YACMACompilerLinkerSettingsRun) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Warray-bounds-pointer-arithmetic) # New warnings in clang 14. _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(-Warray-parameter) + # NOTE: clang 17 enables by default a new compiler flag called "-fassume-unique-vtables": + # + # https://releases.llvm.org/17.0.1/tools/clang/docs/ReleaseNotes.html#c-language-changes + # + # This flag however seems to be buggy: + # + # https://github.com/llvm/llvm-project/issues/71196 + # + # On our part, in several projects we are experiencing Boost.serialization failures when + # (de)serialising derived objects through pointers to bases. Thus, we forcibly disable + # this new flag. + _YACMA_CHECK_ENABLE_CXX_FLAG(-fno-assume-unique-vtables) endif() # Common configuration for GCC, clang and Intel. @@ -198,6 +210,12 @@ if(NOT _YACMACompilerLinkerSettingsRun) _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(/W4) # Treat warnings as errors. _YACMA_CHECK_ENABLE_DEBUG_CXX_FLAG(/WX) + # Strict conformance mode. + _YACMA_CHECK_ENABLE_CXX_FLAG(/permissive-) + # Strict preprocessor conformance mode. + # NOTE: this seems to cause issues in some + # projects, disable for now. + # _YACMA_CHECK_ENABLE_CXX_FLAG(/Zc:preprocessor) endif() # Set the cache variables. From 014c36a85f30bef06f1a4c902888d7ef3603d0f5 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 20 Dec 2025 10:37:55 +0100 Subject: [PATCH 14/18] Partially restore the copy/move consistency checks. These allow us to move certain checks from runtime to compile time. --- include/tanuki/tanuki.hpp | 59 +++++++++++++++++++++++---------- test/test_no_copy_move_swap.cpp | 8 ++--- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 8700208..8902a75 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -641,9 +641,11 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) return new _tanuki_holder(_tanuki_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. + unreachable(); // LCOV_EXCL_LINE } // Same as above, but return a shared ptr. [[nodiscard]] std::shared_ptr<_tanuki_value_iface> _tanuki_shared_clone_holder() const final @@ -651,6 +653,11 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface) { return std::make_shared<_tanuki_holder>(_tanuki_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"); } } @@ -665,14 +672,14 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface *_tanuki_move_init_holder(void *ptr) && noexcept final { if constexpr (std::is_move_constructible_v) { @@ -682,9 +689,11 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_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 copy/move-constructible, so must be the value type. +template +concept copy_move_consistent = (Cfg.semantics == wrap_semantics::reference) + || ((!Cfg.copy_constructible || std::is_copy_constructible_v) + && (!Cfg.move_constructible || std::is_move_constructible_v)); + } // namespace detail // The wrap class. @@ -1354,8 +1370,8 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface = std::move(*pv_iface)._tanuki_move_init_holder(this->static_storage); // NOTE: when we loaded the serialised pointer, the value contained in the holder was deserialised @@ -1438,7 +1454,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage) && // We must be able to construct the holder. - std::constructible_from> + std::constructible_from> && + // Check copy/move consistency. + detail::copy_move_consistent wrap() noexcept(noexcept(this->ctor_impl()) && detail::nothrow_default_initializable) { ctor_impl(); @@ -1454,7 +1472,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage, wrap>) && // We must be able to construct the holder. - std::constructible_from>, T &&> + std::constructible_from>, T &&> && + // Check copy/move consistency. + detail::copy_move_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))) @@ -1467,6 +1487,8 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage requires std::default_initializable && // We must be able to construct the holder. @@ -1488,7 +1510,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage && std::same_as> && std::default_initializable && // We must be able to construct the holder. - std::constructible_from, U &&...> + std::constructible_from, U &&...> && + // Check copy/move consistency. + detail::copy_move_consistent explicit wrap(std::in_place_type_t, U &&...args) noexcept(noexcept(this->ctor_impl(std::forward(args)...)) && detail::nothrow_default_initializable) @@ -1638,8 +1662,6 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storagem_pv_iface)) { destroy_and_move_init(); } @@ -1733,7 +1755,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage, wrap>) && // We must be able to construct the holder. - std::constructible_from>, T &&> + std::constructible_from>, T &&> && + // Check copy/move consistency. + detail::copy_move_consistent, Cfg> wrap &operator=(T &&x) { if constexpr (Cfg.semantics == wrap_semantics::value) { @@ -1816,8 +1840,9 @@ class TANUKI_VISIBLE wrap : private detail::wrap_storage && std::same_as> && // We must be able to construct the holder. - std::constructible_from, Args &&...> + std::constructible_from, Args &&...> && // Check copy/move consistency. + detail::copy_move_consistent friend void emplace(wrap &w, Args &&...args) noexcept(noexcept(w.ctor_impl(std::forward(args)...))) { if constexpr (Cfg.semantics == wrap_semantics::value) { diff --git a/test/test_no_copy_move_swap.cpp b/test/test_no_copy_move_swap.cpp index bfd7bb9..2ff6df1 100644 --- a/test/test_no_copy_move_swap.cpp +++ b/test/test_no_copy_move_swap.cpp @@ -43,8 +43,8 @@ TEST_CASE("noncopyable") using wrap_t = tanuki::wrap; REQUIRE(!std::constructible_from); - REQUIRE(std::constructible_from>); - REQUIRE(emplaceable); + REQUIRE(!std::constructible_from>); + REQUIRE(!emplaceable); REQUIRE(!tanuki::valid_config{.move_constructible = false, .move_assignable = true}>); REQUIRE(!tanuki::valid_config{.copy_constructible = false, .copy_assignable = true}>); @@ -84,8 +84,8 @@ TEST_CASE("nonmovable") using wrap_t = tanuki::wrap; REQUIRE(!std::constructible_from); - REQUIRE(std::constructible_from>); - REQUIRE(emplaceable); + REQUIRE(!std::constructible_from>); + REQUIRE(!emplaceable); using wrap2_t = tanuki::wrap{.copy_constructible = false, .copy_assignable = false, From a7be3bb65f6b96f8d4f88cad404f8f315c3014cb Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 20 Dec 2025 10:54:06 +0100 Subject: [PATCH 15/18] tidy fix. --- test/test_gen_assignment.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_gen_assignment.cpp b/test/test_gen_assignment.cpp index 8b8bb40..b19f63a 100644 --- a/test/test_gen_assignment.cpp +++ b/test/test_gen_assignment.cpp @@ -15,7 +15,7 @@ #endif -// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,bugprone-crtp-constructor-accessibility) +// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,bugprone-crtp-constructor-accessibility,bugprone-exception-escape) template struct any_iface_impl : Base { @@ -139,7 +139,7 @@ TEST_CASE("gen assignment") } } -// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,bugprone-crtp-constructor-accessibility) +// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while,bugprone-crtp-constructor-accessibility,bugprone-exception-escape) #if defined(__GNUC__) From 53adb6e60cd8f0bf5470c8881669b5baa844fdf6 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 20 Dec 2025 13:48:59 +0100 Subject: [PATCH 16/18] Test additions. --- include/tanuki/tanuki.hpp | 15 ++++++-- test/CMakeLists.txt | 1 + test/test_non_assignable.cpp | 67 ++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 test/test_non_assignable.cpp diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 8902a75..986a4e9 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -759,7 +759,12 @@ struct TANUKI_VISIBLE _tanuki_holder final : public impl_from_iface_tanuki_swap_value(w1.m_pv_iface)) { // The internal value does not support swapping. Resort to the canonical implementation. - canonical_swap(); + // + // NOTE: at the moment I cannot find a way to trigger this, because we end up here only if a + // swappable wrap contains a non-swappable value. But: a wrap is marked as swappable only if it + // is move ctible/assignable, which requires a move ctible/assignable value, which (almost?) + // always means that the value is swappable as well. There may be some way to construct a + // pathological type that triggers this branch, but so far I have not found it. + canonical_swap(); // LCOV_EXCL_LINE } } else { // For dynamic storage, swap the pointers. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 80fe114..466d9dc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,6 +39,7 @@ 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) +ADD_TANUKI_TESTCASE(test_non_assignable) ADD_TANUKI_TESTCASE(test_ref_iface) ADD_TANUKI_TESTCASE(test_trait_like) ADD_TANUKI_TESTCASE(test_composite) diff --git a/test/test_non_assignable.cpp b/test/test_non_assignable.cpp new file mode 100644 index 0000000..ae7eba8 --- /dev/null +++ b/test/test_non_assignable.cpp @@ -0,0 +1,67 @@ +#include + +#include + +#include + +// NOLINTBEGIN(misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) + +// NOTE: these are tests for value type which do not support assignment/swap. We want to check that the wrap +// assignment/swap primitives keep on working ok. + +template +struct any_iface_impl { +}; + +struct any_iface { + template + struct impl : Base { + }; +}; + +using wrap_t = tanuki::wrap; + +struct non_copy_assignable { + int n = 0; + + explicit non_copy_assignable(int n = 0) : n(n) {} + non_copy_assignable(const non_copy_assignable &) = default; + non_copy_assignable(non_copy_assignable &&) = default; + non_copy_assignable &operator=(const non_copy_assignable &) = delete; + non_copy_assignable &operator=(non_copy_assignable &&) = default; + ~non_copy_assignable() = default; +}; + +TEST_CASE("copy_assignment") +{ + wrap_t w1{non_copy_assignable{}}, w2{non_copy_assignable{42}}; + w1 = w2; + REQUIRE(value_ref(w1).n == 42); + + non_copy_assignable tmp{123}; + w1 = tmp; + REQUIRE(value_ref(w1).n == 123); +} + +struct non_move_assignable { + int n = 0; + + explicit non_move_assignable(int n = 0) : n(n) {} + non_move_assignable(const non_move_assignable &) = default; + non_move_assignable(non_move_assignable &&) = default; + non_move_assignable &operator=(const non_move_assignable &) = default; + non_move_assignable &operator=(non_move_assignable &&) = delete; + ~non_move_assignable() = default; +}; + +TEST_CASE("move_assignment") +{ + wrap_t w1{non_move_assignable{}}, w2{non_move_assignable{42}}; + w1 = std::move(w2); + REQUIRE(value_ref(w1).n == 42); + + w1 = non_move_assignable{123}; + REQUIRE(value_ref(w1).n == 123); +} + +// NOLINTEND(misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) From c5770d7a3f9d369cca51b734a81efa60959e4799 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 20 Dec 2025 13:56:59 +0100 Subject: [PATCH 17/18] tidy fix. --- test/test_non_assignable.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_non_assignable.cpp b/test/test_non_assignable.cpp index ae7eba8..21e7d86 100644 --- a/test/test_non_assignable.cpp +++ b/test/test_non_assignable.cpp @@ -4,7 +4,7 @@ #include -// NOLINTBEGIN(misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) +// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) // NOTE: these are tests for value type which do not support assignment/swap. We want to check that the wrap // assignment/swap primitives keep on working ok. @@ -64,4 +64,4 @@ TEST_CASE("move_assignment") REQUIRE(value_ref(w1).n == 123); } -// NOLINTEND(misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) +// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) From 773af0efd9f5088d937d7097e9ef08eb3841d792 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sat, 20 Dec 2025 14:12:43 +0100 Subject: [PATCH 18/18] Internal doc bit. --- 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 986a4e9..206821b 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -282,7 +282,8 @@ struct TANUKI_VISIBLE _tanuki_value_iface : public IFace { virtual ~_tanuki_value_iface() noexcept = default; // NOTE: we want to provide an implementation for the virtual functions (instead of keeping them pure virtual). This - // allows us to check for correct interface implementations through their default-constructibility. + // allows us to check for correct interface implementations through their default-constructibility, and to determine + // the noexcept-ness of the _tanuki_holder constructors. // LCOV_EXCL_START