From 02207ae8c2f481ad99c9869c997ac5c355e5ce97 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 15:31:27 +0100 Subject: [PATCH 01/59] add: unique_handle class skeleton and test file Signed-off-by: DNKpp --- include/unique_handle.hpp | 31 +++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/unique_handle.cpp | 25 +++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 include/unique_handle.hpp create mode 100644 tests/unique_handle.cpp diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp new file mode 100644 index 000000000..7210cc7df --- /dev/null +++ b/include/unique_handle.hpp @@ -0,0 +1,31 @@ +// Copyright Dominic Koepke 2019 - 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SL_UNIQUE_HANDLE_HPP +#define SL_UNIQUE_HANDLE_HPP + +#pragma once + +#include +#include + +namespace sl +{ + template + class unique_handle + { + public: + [[nodiscard]] + constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } + + [[nodiscard]] + constexpr bool has_value() const noexcept { return m_Value.has_value(); } + + private: + std::optional m_Value{}; + }; +} + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 038c71d07..8a9ba02a6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable( "main.cpp" "concepts/shift_operators.cpp" "concepts/logical_arithmetic_operators.cpp" + "unique_handle.cpp" ) target_link_libraries( diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp new file mode 100644 index 000000000..e7eb592ec --- /dev/null +++ b/tests/unique_handle.cpp @@ -0,0 +1,25 @@ +// Copyright Dominic Koepke 2019 - 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include + +#include "unique_handle.hpp" + +using namespace sl; + +TEST_CASE("unique_handle should be default constructible.", "[unique_handle]") +{ + unique_handle handle{}; + + // nothing to check here +} + +TEST_CASE("default constructed unique_handle should not contain a value.", "[unique_handle]") +{ + const unique_handle handle{}; + + REQUIRE(!handle.has_value()); + REQUIRE(!handle); +} From 0ee506e0139aacca99fbc862a9b29386b7971620 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 16:18:28 +0100 Subject: [PATCH 02/59] disable copy ctor and assign of unique_handle Signed-off-by: DNKpp --- include/unique_handle.hpp | 5 +++++ tests/unique_handle.cpp | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 7210cc7df..f6e3d4825 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -17,6 +17,11 @@ namespace sl class unique_handle { public: + constexpr unique_handle() noexcept = default; + + unique_handle(const unique_handle&) = delete; + unique_handle& operator =(const unique_handle&) = delete; + [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index e7eb592ec..a0674cca6 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -11,14 +11,20 @@ using namespace sl; TEST_CASE("unique_handle should be default constructible.", "[unique_handle]") { - unique_handle handle{}; + constexpr unique_handle handle{}; // nothing to check here } +TEST_CASE("unique_handle should neither be copy constructible nor assignable.", "[unique_handle]") +{ + REQUIRE(!std::copy_constructible>); + REQUIRE(!std::assignable_from&, const unique_handle&>); +} + TEST_CASE("default constructed unique_handle should not contain a value.", "[unique_handle]") { - const unique_handle handle{}; + constexpr unique_handle handle{}; REQUIRE(!handle.has_value()); REQUIRE(!handle); From 584ca0bc8afac5c8017dd9cfaf7e38d5a259c58f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 16:19:22 +0100 Subject: [PATCH 03/59] make unique_handle constructible by nullhandle_t Signed-off-by: DNKpp --- include/unique_handle.hpp | 13 +++++++++++++ tests/unique_handle.cpp | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index f6e3d4825..9187842d0 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -13,12 +13,25 @@ namespace sl { + // ReSharper disable once IdentifierTypo + struct nullhandle_t + { + }; + + // ReSharper disable once IdentifierTypo + constexpr nullhandle_t nullhandle{}; + template class unique_handle { public: constexpr unique_handle() noexcept = default; + constexpr unique_handle(nullhandle_t) noexcept + : m_Value{ std::nullopt } + { + } + unique_handle(const unique_handle&) = delete; unique_handle& operator =(const unique_handle&) = delete; diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index a0674cca6..2583979a5 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -29,3 +29,11 @@ TEST_CASE("default constructed unique_handle should not contain a value.", "[uni REQUIRE(!handle.has_value()); REQUIRE(!handle); } + +TEST_CASE("unique_handle should be explicitly null constructible by nullhandle.", "[unique_handle]") +{ + constexpr unique_handle handle{ nullhandle }; + + REQUIRE(!handle.has_value()); + REQUIRE(!handle); +} From e77692e6412008e89b2c82ec76c6273a337e781f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 16:21:19 +0100 Subject: [PATCH 04/59] add default ctor to unique_handle Signed-off-by: DNKpp --- include/unique_handle.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 9187842d0..30861c94e 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -26,6 +26,7 @@ namespace sl { public: constexpr unique_handle() noexcept = default; + constexpr ~unique_handle() noexcept = default; constexpr unique_handle(nullhandle_t) noexcept : m_Value{ std::nullopt } From e81d527b428286c299b894eeb4bbdca2972de6a3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 17:38:20 +0100 Subject: [PATCH 05/59] add&test: symmetrical concept constructible_by Signed-off-by: DNKpp --- .../concepts/stl_symmetrical.hpp | 19 +++++++++++++ tests/CMakeLists.txt | 1 + tests/concepts/stl_symmetrical.cpp | 28 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 include/Simple-Utility/concepts/stl_symmetrical.hpp create mode 100644 tests/concepts/stl_symmetrical.cpp diff --git a/include/Simple-Utility/concepts/stl_symmetrical.hpp b/include/Simple-Utility/concepts/stl_symmetrical.hpp new file mode 100644 index 000000000..ca323956e --- /dev/null +++ b/include/Simple-Utility/concepts/stl_symmetrical.hpp @@ -0,0 +1,19 @@ +// Copyright Dominic Koepke 2019 - 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef SL_UNIQUE_HANDLE_HPP +#define SL_UNIQUE_HANDLE_HPP + +#pragma once + +#include + +namespace sl::concepts +{ + template + concept constructible_by = std::constructible_from; +} + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8a9ba02a6..3da6fc0ef 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable( "concepts/shift_operators.cpp" "concepts/logical_arithmetic_operators.cpp" "unique_handle.cpp" + "concepts/stl_symmetrical.cpp" ) target_link_libraries( diff --git a/tests/concepts/stl_symmetrical.cpp b/tests/concepts/stl_symmetrical.cpp new file mode 100644 index 000000000..c61727062 --- /dev/null +++ b/tests/concepts/stl_symmetrical.cpp @@ -0,0 +1,28 @@ +// Copyright Dominic Koepke 2019 - 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#include + +#include "../helper.hpp" + +#include "Simple-Utility/concepts/stl_symmetrical.hpp" + +using namespace sl::concepts; + +#pragma warning(disable: 26444) +TEMPLATE_TEST_CASE_SIG +( + "constructible_by should behave like std::constructible_from with one constructor argument", + "[concepts][stl_ext]", + ((class TSource, class TTarget, bool VExpected), TSource, TTarget, VExpected), + (int, int, true), + (fail_t, int, false), + (int, fail_t, false), + (int, float, true) +) +#pragma warning(default: 26444) +{ + REQUIRE(constructible_by == VExpected); +} From c29c7bbebe5c5760da66b6a175ad64426eed8083 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 17:54:11 +0100 Subject: [PATCH 06/59] rename constructible_by to constructs and add docs Signed-off-by: DNKpp --- .../concepts/stl_symmetrical.hpp | 24 ++++++++++++++++++- tests/concepts/stl_symmetrical.cpp | 4 ++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/include/Simple-Utility/concepts/stl_symmetrical.hpp b/include/Simple-Utility/concepts/stl_symmetrical.hpp index ca323956e..e070d4df5 100644 --- a/include/Simple-Utility/concepts/stl_symmetrical.hpp +++ b/include/Simple-Utility/concepts/stl_symmetrical.hpp @@ -10,10 +10,32 @@ #include +// ReSharper disable CppClangTidyClangDiagnosticDocumentation + namespace sl::concepts { + /** + * \addtogroup concepts + * @{ + */ + + /** + * \defgroup stl_symmetrical stl symmetrical + * \brief This group offers symmetrical counterparts concepts to already existing stl concepts. + * @{ + */ + + /** + * \brief Checks whether the target type is constructible from the source type. + * \detail This is the symmetrical counterpart of ``std::constructible_from`` concept with a single constructor argument. + * \tparam TSource The source type handed over to the constructor of the target + * \tparam TTarget The target type to check + */ template - concept constructible_by = std::constructible_from; + concept constructs = std::constructible_from; + + /** @} */ + /** @} */ } #endif diff --git a/tests/concepts/stl_symmetrical.cpp b/tests/concepts/stl_symmetrical.cpp index c61727062..e865bfe9e 100644 --- a/tests/concepts/stl_symmetrical.cpp +++ b/tests/concepts/stl_symmetrical.cpp @@ -14,7 +14,7 @@ using namespace sl::concepts; #pragma warning(disable: 26444) TEMPLATE_TEST_CASE_SIG ( - "constructible_by should behave like std::constructible_from with one constructor argument", + "constructs should behave as the symmetrical counterpart of std::constructible_from with one constructor argument", "[concepts][stl_ext]", ((class TSource, class TTarget, bool VExpected), TSource, TTarget, VExpected), (int, int, true), @@ -24,5 +24,5 @@ TEMPLATE_TEST_CASE_SIG ) #pragma warning(default: 26444) { - REQUIRE(constructible_by == VExpected); + REQUIRE(constructs == VExpected); } From 6d0b0db0c3adda5a3deb22052d05563d2a845bd2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 18:18:17 +0100 Subject: [PATCH 07/59] fix: include guards of concepts/stl_symmetrical.hpp Signed-off-by: DNKpp --- include/Simple-Utility/concepts/stl_symmetrical.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/concepts/stl_symmetrical.hpp b/include/Simple-Utility/concepts/stl_symmetrical.hpp index e070d4df5..5028c7946 100644 --- a/include/Simple-Utility/concepts/stl_symmetrical.hpp +++ b/include/Simple-Utility/concepts/stl_symmetrical.hpp @@ -3,8 +3,8 @@ // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) -#ifndef SL_UNIQUE_HANDLE_HPP -#define SL_UNIQUE_HANDLE_HPP +#ifndef SL_STL_SYMMETRICAL_HPP +#define SL_STL_SYMMETRICAL_HPP #pragma once From e424e05c963d9e0af495dee5acd54727677b3c91 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 19:09:11 +0100 Subject: [PATCH 08/59] add&test&doc: assignable_to concept Signed-off-by: DNKpp --- .../concepts/stl_symmetrical.hpp | 10 +++++ tests/concepts/stl_symmetrical.cpp | 38 +++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/include/Simple-Utility/concepts/stl_symmetrical.hpp b/include/Simple-Utility/concepts/stl_symmetrical.hpp index 5028c7946..768c67098 100644 --- a/include/Simple-Utility/concepts/stl_symmetrical.hpp +++ b/include/Simple-Utility/concepts/stl_symmetrical.hpp @@ -34,6 +34,16 @@ namespace sl::concepts template concept constructs = std::constructible_from; + + /** + * \brief Checks whether the target type is assignable from the source type. + * \detail This is the symmetrical counterpart of ``std::assignable_from`` concept. + * \tparam TSource The source type handed over to the target + * \tparam TTarget The target type to check + */ + template + concept assignable_to = std::assignable_from; + /** @} */ /** @} */ } diff --git a/tests/concepts/stl_symmetrical.cpp b/tests/concepts/stl_symmetrical.cpp index e865bfe9e..ec64e79af 100644 --- a/tests/concepts/stl_symmetrical.cpp +++ b/tests/concepts/stl_symmetrical.cpp @@ -11,6 +11,22 @@ using namespace sl::concepts; +namespace +{ + struct target_t + { + target_t() = default; + target_t(int) + { + } + + target_t& operator =(int) + { + return *this; + } + }; +} + #pragma warning(disable: 26444) TEMPLATE_TEST_CASE_SIG ( @@ -18,11 +34,27 @@ TEMPLATE_TEST_CASE_SIG "[concepts][stl_ext]", ((class TSource, class TTarget, bool VExpected), TSource, TTarget, VExpected), (int, int, true), - (fail_t, int, false), - (int, fail_t, false), - (int, float, true) + (int, target_t, true), + (target_t, int, false) ) #pragma warning(default: 26444) { REQUIRE(constructs == VExpected); } + +#pragma warning(disable: 26444) +TEMPLATE_TEST_CASE_SIG +( + "assignable_to should behave as the symmetrical counterpart of std::assignable_from.", + "[concepts][stl_ext]", + ((class TSource, class TTarget, bool VExpected), TSource, TTarget, VExpected), + (int, int&, true), + (int, target_t&, true), + (int, target_t, false), + (target_t, int&, false) +) +#pragma warning(default: 26444) +{ + REQUIRE(assignable_to == VExpected); +} + From 95b3f9bde0e0eb2c54cb0724f455492a799b8067 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 19:21:31 +0100 Subject: [PATCH 09/59] add&test&docs: not_same_as concept Signed-off-by: DNKpp --- .../Simple-Utility/concepts/stl_symmetrical.hpp | 10 +++++++++- tests/concepts/stl_symmetrical.cpp | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/concepts/stl_symmetrical.hpp b/include/Simple-Utility/concepts/stl_symmetrical.hpp index 768c67098..a28b4b689 100644 --- a/include/Simple-Utility/concepts/stl_symmetrical.hpp +++ b/include/Simple-Utility/concepts/stl_symmetrical.hpp @@ -25,6 +25,15 @@ namespace sl::concepts * @{ */ + /** + * \brief Checks whether the left-hand-side type is unequal to the right-hand-side type. + * \detail This is the inverted counterpart of ``std::same_as`` concept. + * \tparam TLhs The source type handed over to the target + * \tparam TRhs The target type to check + */ + template + concept not_same_as = !std::same_as; + /** * \brief Checks whether the target type is constructible from the source type. * \detail This is the symmetrical counterpart of ``std::constructible_from`` concept with a single constructor argument. @@ -34,7 +43,6 @@ namespace sl::concepts template concept constructs = std::constructible_from; - /** * \brief Checks whether the target type is assignable from the source type. * \detail This is the symmetrical counterpart of ``std::assignable_from`` concept. diff --git a/tests/concepts/stl_symmetrical.cpp b/tests/concepts/stl_symmetrical.cpp index ec64e79af..d01fb6800 100644 --- a/tests/concepts/stl_symmetrical.cpp +++ b/tests/concepts/stl_symmetrical.cpp @@ -16,6 +16,7 @@ namespace struct target_t { target_t() = default; + target_t(int) { } @@ -58,3 +59,18 @@ TEMPLATE_TEST_CASE_SIG REQUIRE(assignable_to == VExpected); } +#pragma warning(disable: 26444) +TEMPLATE_TEST_CASE_SIG +( + "not_same_as should behave as the inverted counterpart of std::same_as.", + "[concepts][stl_ext]", + ((class TSource, class TTarget, bool VExpected), TSource, TTarget, VExpected), + (int, int, false), + (int, target_t, true), + (target_t&, target_t, true), + (const target_t&, const target_t&, false) +) +#pragma warning(default: 26444) +{ + REQUIRE(not_same_as == VExpected); +} From 2ed02a9e8d6b5e04e7fbdc23f415d4e86e96a510 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 19:23:49 +0100 Subject: [PATCH 10/59] refactor: rename stl_symmetrical to stl_counterparts Signed-off-by: DNKpp --- .../concepts/{stl_symmetrical.hpp => stl_counterparts.hpp} | 4 ++-- tests/CMakeLists.txt | 2 +- tests/concepts/{stl_symmetrical.cpp => stl_counterparts.cpp} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename include/Simple-Utility/concepts/{stl_symmetrical.hpp => stl_counterparts.hpp} (92%) rename tests/concepts/{stl_symmetrical.cpp => stl_counterparts.cpp} (96%) diff --git a/include/Simple-Utility/concepts/stl_symmetrical.hpp b/include/Simple-Utility/concepts/stl_counterparts.hpp similarity index 92% rename from include/Simple-Utility/concepts/stl_symmetrical.hpp rename to include/Simple-Utility/concepts/stl_counterparts.hpp index a28b4b689..e2ed45916 100644 --- a/include/Simple-Utility/concepts/stl_symmetrical.hpp +++ b/include/Simple-Utility/concepts/stl_counterparts.hpp @@ -20,8 +20,8 @@ namespace sl::concepts */ /** - * \defgroup stl_symmetrical stl symmetrical - * \brief This group offers symmetrical counterparts concepts to already existing stl concepts. + * \defgroup stl_counterpart_concepts stl counterparts + * \brief This group offers counterpart concepts for existing stl concepts. * @{ */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3da6fc0ef..d95662e74 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ add_executable( "concepts/shift_operators.cpp" "concepts/logical_arithmetic_operators.cpp" "unique_handle.cpp" - "concepts/stl_symmetrical.cpp" + "concepts/stl_counterparts.cpp" ) target_link_libraries( diff --git a/tests/concepts/stl_symmetrical.cpp b/tests/concepts/stl_counterparts.cpp similarity index 96% rename from tests/concepts/stl_symmetrical.cpp rename to tests/concepts/stl_counterparts.cpp index d01fb6800..f7f091c1f 100644 --- a/tests/concepts/stl_symmetrical.cpp +++ b/tests/concepts/stl_counterparts.cpp @@ -7,7 +7,7 @@ #include "../helper.hpp" -#include "Simple-Utility/concepts/stl_symmetrical.hpp" +#include "Simple-Utility/concepts/stl_counterparts.hpp" using namespace sl::concepts; From a65bb178b290bc57818a0548d56fad4072363cab Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 19:41:11 +0100 Subject: [PATCH 11/59] add&test: unique_handle ctor and assignment for its value types Signed-off-by: DNKpp --- include/unique_handle.hpp | 27 +++++++++++++++++++++++++-- tests/unique_handle.cpp | 22 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 30861c94e..4c05ef461 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -11,6 +11,8 @@ #include #include +#include "Simple-Utility/concepts/stl_counterparts.hpp" + namespace sl { // ReSharper disable once IdentifierTypo @@ -27,14 +29,35 @@ namespace sl public: constexpr unique_handle() noexcept = default; constexpr ~unique_handle() noexcept = default; + constexpr unique_handle(unique_handle&&) noexcept = default; + constexpr unique_handle& operator =(unique_handle&&) noexcept = default; + + unique_handle(const unique_handle&) = delete; + unique_handle& operator =(const unique_handle&) = delete; constexpr unique_handle(nullhandle_t) noexcept : m_Value{ std::nullopt } { } - unique_handle(const unique_handle&) = delete; - unique_handle& operator =(const unique_handle&) = delete; + template T2> + requires concepts::not_same_as, unique_handle> + && concepts::not_same_as, nullhandle_t> + explicit (!std::convertible_to) + constexpr unique_handle(T2&& value) + : m_Value{ std::forward(value) } + { + } + + template T2> + requires concepts::not_same_as, unique_handle> + && concepts::not_same_as, nullhandle_t> + && concepts::constructs, nullhandle_t> + constexpr unique_handle& operator =(T2&& value) + { + m_Value = std::forward(value); + return *this; + } [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 2583979a5..19860c9fc 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -37,3 +37,25 @@ TEST_CASE("unique_handle should be explicitly null constructible by nullhandle." REQUIRE(!handle.has_value()); REQUIRE(!handle); } + +TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") +{ + constexpr unique_handle handle{ 42 }; + + REQUIRE(handle.has_value()); + REQUIRE(handle); +} + +TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") +{ + constexpr unique_handle handle = [] + { + // ReSharper disable once CppInitializedValueIsAlwaysRewritten + unique_handle temp{}; + temp = 42; + return temp; + }(); + + REQUIRE(handle.has_value()); + REQUIRE(handle); +} From e45194f14753e81a83012d44b205089ce77af7fe Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 19:57:02 +0100 Subject: [PATCH 12/59] add&test: unique_handle assignable from nullhandle_t Signed-off-by: DNKpp --- include/unique_handle.hpp | 13 +++++++++++++ tests/unique_handle.cpp | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 4c05ef461..45b143b23 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -13,6 +13,13 @@ #include "Simple-Utility/concepts/stl_counterparts.hpp" +// some of the std::optional interface hasn't declared constexpr before +#if __cpp_lib_optional >= 202106 +#define SL_UNIQUE_HANDLE_FULL_CONSTEXPR constexpr +#else +#define SL_UNIQUE_HANDLE_FULL_CONSTEXPR +#endif + namespace sl { // ReSharper disable once IdentifierTypo @@ -40,6 +47,12 @@ namespace sl { } + SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator =(nullhandle_t) noexcept + { + m_Value = std::nullopt; + return *this; + } + template T2> requires concepts::not_same_as, unique_handle> && concepts::not_same_as, nullhandle_t> diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 19860c9fc..61ac947a1 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -38,6 +38,20 @@ TEST_CASE("unique_handle should be explicitly null constructible by nullhandle." REQUIRE(!handle); } +TEST_CASE("unique_handle should be assignable by nullhandle.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR const unique_handle handle = [] + { + // ReSharper disable once CppInitializedValueIsAlwaysRewritten + unique_handle temp{}; + temp = nullhandle; + return temp; + }(); + + REQUIRE(!handle.has_value()); + REQUIRE(!handle); +} + TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") { constexpr unique_handle handle{ 42 }; From 7f6762ccf61af670aeeff3fcd25b78a3d97f27fa Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 20:11:36 +0100 Subject: [PATCH 13/59] test: use STATIC_REQUIRE where appropriate for unique_handle tests Signed-off-by: DNKpp --- tests/unique_handle.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 61ac947a1..4009e288f 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -5,6 +5,8 @@ #include +#include "helper.hpp" + #include "unique_handle.hpp" using namespace sl; @@ -18,24 +20,24 @@ TEST_CASE("unique_handle should be default constructible.", "[unique_handle]") TEST_CASE("unique_handle should neither be copy constructible nor assignable.", "[unique_handle]") { - REQUIRE(!std::copy_constructible>); - REQUIRE(!std::assignable_from&, const unique_handle&>); + STATIC_REQUIRE(!std::copy_constructible>); + STATIC_REQUIRE(!std::assignable_from&, const unique_handle&>); } TEST_CASE("default constructed unique_handle should not contain a value.", "[unique_handle]") { constexpr unique_handle handle{}; - REQUIRE(!handle.has_value()); - REQUIRE(!handle); + STATIC_REQUIRE(!handle.has_value()); + STATIC_REQUIRE(!handle); } TEST_CASE("unique_handle should be explicitly null constructible by nullhandle.", "[unique_handle]") { constexpr unique_handle handle{ nullhandle }; - REQUIRE(!handle.has_value()); - REQUIRE(!handle); + STATIC_REQUIRE(!handle.has_value()); + STATIC_REQUIRE(!handle); } TEST_CASE("unique_handle should be assignable by nullhandle.", "[unique_handle]") @@ -56,8 +58,8 @@ TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") { constexpr unique_handle handle{ 42 }; - REQUIRE(handle.has_value()); - REQUIRE(handle); + STATIC_REQUIRE(handle.has_value()); + STATIC_REQUIRE(handle); } TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") @@ -70,6 +72,6 @@ TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") return temp; }(); - REQUIRE(handle.has_value()); - REQUIRE(handle); + STATIC_REQUIRE(handle.has_value()); + STATIC_REQUIRE(handle); } From 8632b0f886765fec46d86b1bca2279feae460348 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 20:14:27 +0100 Subject: [PATCH 14/59] test: use better testing method for default constructibility of unique_handle Signed-off-by: DNKpp --- tests/unique_handle.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 4009e288f..8fe7090f6 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -13,9 +13,7 @@ using namespace sl; TEST_CASE("unique_handle should be default constructible.", "[unique_handle]") { - constexpr unique_handle handle{}; - - // nothing to check here + STATIC_REQUIRE(std::default_initializable>); } TEST_CASE("unique_handle should neither be copy constructible nor assignable.", "[unique_handle]") From 7ee3c4e9f533acc7e5f18ff3c87b2e5d2fe2d0f4 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 21:22:10 +0100 Subject: [PATCH 15/59] add&test: unique_handle raw function Signed-off-by: DNKpp --- include/unique_handle.hpp | 8 ++++++++ tests/unique_handle.cpp | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 45b143b23..e66419064 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -30,6 +30,8 @@ namespace sl // ReSharper disable once IdentifierTypo constexpr nullhandle_t nullhandle{}; + using bad_handle_access = std::bad_optional_access; + template class unique_handle { @@ -72,6 +74,12 @@ namespace sl return *this; } + [[nodiscard]] + constexpr const T& raw() const + { + return m_Value.value(); + } + [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 8fe7090f6..810d3e110 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -73,3 +73,19 @@ TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") STATIC_REQUIRE(handle.has_value()); STATIC_REQUIRE(handle); } + +TEST_CASE("unique_handle::raw should expose a const reference of its value.", "[unique_handle]") +{ + constexpr unique_handle handle{ 1337 }; + + constexpr const int& ref{ handle.raw() }; + + STATIC_REQUIRE(ref == 1337); +} + +TEST_CASE("unique_handle::raw should throw bad_handle_access if no value is hold.", "[unique_handle]") +{ + constexpr unique_handle handle{}; + + REQUIRE_THROWS_AS(handle.raw(), bad_handle_access); +} From b62d4b4d1275efb77e0b94b50dd2f8f9fde8e837 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 21:23:30 +0100 Subject: [PATCH 16/59] add&test: unique_handle operator * Signed-off-by: DNKpp --- include/unique_handle.hpp | 6 ++++++ tests/unique_handle.cpp | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index e66419064..3055794e6 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -80,6 +80,12 @@ namespace sl return m_Value.value(); } + [[nodiscard]] + constexpr const T& operator *() const + { + return *m_Value; + } + [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 810d3e110..64cbf865b 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -89,3 +89,12 @@ TEST_CASE("unique_handle::raw should throw bad_handle_access if no value is hold REQUIRE_THROWS_AS(handle.raw(), bad_handle_access); } + +TEST_CASE("unique_handle's operator * should expose a const reference of its value.", "[unique_handle]") +{ + constexpr unique_handle handle{ 42 }; + + constexpr const int& ref{ *handle }; + + STATIC_REQUIRE(ref == 42); +} From 93e1f22a5722471f8ce36eb2b81c6d106e2217f0 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 21:42:47 +0100 Subject: [PATCH 17/59] add&test: operator -> of unique_handle Signed-off-by: DNKpp --- include/unique_handle.hpp | 12 ++++++++++++ tests/unique_handle.cpp | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 3055794e6..318c6ed77 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -86,6 +86,18 @@ namespace sl return *m_Value; } + [[nodiscard]] + constexpr T* operator ->() + { + return &*m_Value; + } + + [[nodiscard]] + constexpr const T* operator ->() const + { + return &*m_Value; + } + [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 64cbf865b..47b46b6e8 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -98,3 +98,19 @@ TEST_CASE("unique_handle's operator * should expose a const reference of its val STATIC_REQUIRE(ref == 42); } + +TEST_CASE("unique_handle's operator -> const overload should expose a const pointer to its value.", "[unique_handle]") +{ + constexpr auto access_via_ptr = [] class THandle>() + { + THandle handle{ 1337 }; + auto* ptr = handle.operator ->(); + return *ptr; + }; + + constexpr int non_const_result = access_via_ptr.operator()(); + constexpr int const_result = access_via_ptr.operator()(); + + STATIC_REQUIRE(non_const_result == 1337); + STATIC_REQUIRE(const_result == 1337); +} From 0c0de377a9922afe306f22203d07374c60120937 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 28 Dec 2021 21:52:16 +0100 Subject: [PATCH 18/59] add&test: unique_handle reset function Signed-off-by: DNKpp --- include/unique_handle.hpp | 8 +++++++- tests/unique_handle.cpp | 23 ++++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 318c6ed77..cd9063d9a 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -102,7 +102,13 @@ namespace sl constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } [[nodiscard]] - constexpr bool has_value() const noexcept { return m_Value.has_value(); } + constexpr bool is_valid() const noexcept { return m_Value.has_value(); } + + [[nodiscard]] + SL_UNIQUE_HANDLE_FULL_CONSTEXPR void reset() noexcept + { + m_Value.reset(); + } private: std::optional m_Value{}; diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 47b46b6e8..12980d527 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -26,7 +26,7 @@ TEST_CASE("default constructed unique_handle should not contain a value.", "[uni { constexpr unique_handle handle{}; - STATIC_REQUIRE(!handle.has_value()); + STATIC_REQUIRE(!handle.is_valid()); STATIC_REQUIRE(!handle); } @@ -34,7 +34,7 @@ TEST_CASE("unique_handle should be explicitly null constructible by nullhandle." { constexpr unique_handle handle{ nullhandle }; - STATIC_REQUIRE(!handle.has_value()); + STATIC_REQUIRE(!handle.is_valid()); STATIC_REQUIRE(!handle); } @@ -48,7 +48,7 @@ TEST_CASE("unique_handle should be assignable by nullhandle.", "[unique_handle]" return temp; }(); - REQUIRE(!handle.has_value()); + REQUIRE(!handle.is_valid()); REQUIRE(!handle); } @@ -56,7 +56,7 @@ TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") { constexpr unique_handle handle{ 42 }; - STATIC_REQUIRE(handle.has_value()); + STATIC_REQUIRE(handle.is_valid()); STATIC_REQUIRE(handle); } @@ -70,7 +70,7 @@ TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") return temp; }(); - STATIC_REQUIRE(handle.has_value()); + STATIC_REQUIRE(handle.is_valid()); STATIC_REQUIRE(handle); } @@ -114,3 +114,16 @@ TEST_CASE("unique_handle's operator -> const overload should expose a const poin STATIC_REQUIRE(non_const_result == 1337); STATIC_REQUIRE(const_result == 1337); } + +TEST_CASE("unique_handle::reset should reset to a nullhandle.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const unique_handle handle = [] + { + unique_handle temp{ 42 }; + temp.reset(); + return temp; + }(); + + REQUIRE(!handle); +} From 17327b32bd0ff083540c7afcda5706155ed0fe4d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 00:42:29 +0100 Subject: [PATCH 19/59] docs: fix invalid keyword Signed-off-by: DNKpp --- include/Simple-Utility/concepts/stl_counterparts.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Simple-Utility/concepts/stl_counterparts.hpp b/include/Simple-Utility/concepts/stl_counterparts.hpp index e2ed45916..80f362336 100644 --- a/include/Simple-Utility/concepts/stl_counterparts.hpp +++ b/include/Simple-Utility/concepts/stl_counterparts.hpp @@ -27,7 +27,7 @@ namespace sl::concepts /** * \brief Checks whether the left-hand-side type is unequal to the right-hand-side type. - * \detail This is the inverted counterpart of ``std::same_as`` concept. + * \details This is the inverted counterpart of ``std::same_as`` concept. * \tparam TLhs The source type handed over to the target * \tparam TRhs The target type to check */ @@ -36,7 +36,7 @@ namespace sl::concepts /** * \brief Checks whether the target type is constructible from the source type. - * \detail This is the symmetrical counterpart of ``std::constructible_from`` concept with a single constructor argument. + * \details This is the symmetrical counterpart of ``std::constructible_from`` concept with a single constructor argument. * \tparam TSource The source type handed over to the constructor of the target * \tparam TTarget The target type to check */ @@ -45,7 +45,7 @@ namespace sl::concepts /** * \brief Checks whether the target type is assignable from the source type. - * \detail This is the symmetrical counterpart of ``std::assignable_from`` concept. + * \details This is the symmetrical counterpart of ``std::assignable_from`` concept. * \tparam TSource The source type handed over to the target * \tparam TTarget The target type to check */ From 81f292a234f5074ad93c8758220f454b6def9c57 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 00:43:45 +0100 Subject: [PATCH 20/59] add&test: delete action handling for unique_handle Signed-off-by: DNKpp --- include/unique_handle.hpp | 70 ++++++++++++- tests/unique_handle.cpp | 210 ++++++++++++++++++++++++++++++++++---- 2 files changed, 257 insertions(+), 23 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index cd9063d9a..d47a3955a 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -32,14 +32,47 @@ namespace sl using bad_handle_access = std::bad_optional_access; - template + template + struct default_delete_action + { + constexpr void operator ()(T&) const noexcept + { + } + }; + + template TDeleteAction = default_delete_action> + requires std::copyable class unique_handle { public: + using element_type = T; + using delete_action_type = TDeleteAction; + constexpr unique_handle() noexcept = default; - constexpr ~unique_handle() noexcept = default; - constexpr unique_handle(unique_handle&&) noexcept = default; - constexpr unique_handle& operator =(unique_handle&&) noexcept = default; + + constexpr ~unique_handle() noexcept + { + invoke_delete_action_if_necessary(); + } + + SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle(unique_handle&& other) noexcept + : m_Value{ std::exchange(other.m_Value, std::nullopt) }, + m_DeleteAction{ other.m_DeleteAction } + { + other.m_Value.reset(); + } + + SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator =(unique_handle&& other) noexcept + { + if (this != &other) + { + invoke_delete_action_if_necessary(); + + m_Value = std::exchange(other.m_Value, std::nullopt); + m_DeleteAction = other.m_DeleteAction; + } + return *this; + } unique_handle(const unique_handle&) = delete; unique_handle& operator =(const unique_handle&) = delete; @@ -51,6 +84,8 @@ namespace sl SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator =(nullhandle_t) noexcept { + invoke_delete_action_if_necessary(); + m_Value = std::nullopt; return *this; } @@ -67,9 +102,11 @@ namespace sl template T2> requires concepts::not_same_as, unique_handle> && concepts::not_same_as, nullhandle_t> - && concepts::constructs, nullhandle_t> + && concepts::constructs, T> constexpr unique_handle& operator =(T2&& value) { + invoke_delete_action_if_necessary(); + m_Value = std::forward(value); return *this; } @@ -107,11 +144,34 @@ namespace sl [[nodiscard]] SL_UNIQUE_HANDLE_FULL_CONSTEXPR void reset() noexcept { + invoke_delete_action_if_necessary(); + m_Value.reset(); } + [[nodiscard]] + constexpr delete_action_type& delete_action() noexcept + { + return m_DeleteAction; + } + + [[nodiscard]] + constexpr const delete_action_type& delete_action() const noexcept + { + return m_DeleteAction; + } + private: std::optional m_Value{}; + + [[no_unique_address]] + TDeleteAction m_DeleteAction{}; + + constexpr void invoke_delete_action_if_necessary() noexcept + { + if (m_Value) + std::invoke(m_DeleteAction, *m_Value); + } }; } diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 12980d527..94e1fdfea 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -11,20 +11,36 @@ using namespace sl; +namespace +{ + struct delete_action_mock + { + int* invoke_counter{}; + + constexpr void operator ()(auto&) noexcept + { + if (invoke_counter) + ++(*invoke_counter); + } + }; + + using test_handle = unique_handle; +} + TEST_CASE("unique_handle should be default constructible.", "[unique_handle]") { - STATIC_REQUIRE(std::default_initializable>); + STATIC_REQUIRE(std::default_initializable); } TEST_CASE("unique_handle should neither be copy constructible nor assignable.", "[unique_handle]") { - STATIC_REQUIRE(!std::copy_constructible>); - STATIC_REQUIRE(!std::assignable_from&, const unique_handle&>); + STATIC_REQUIRE(!std::copy_constructible); + STATIC_REQUIRE(!std::assignable_from); } TEST_CASE("default constructed unique_handle should not contain a value.", "[unique_handle]") { - constexpr unique_handle handle{}; + constexpr test_handle handle{}; STATIC_REQUIRE(!handle.is_valid()); STATIC_REQUIRE(!handle); @@ -32,7 +48,7 @@ TEST_CASE("default constructed unique_handle should not contain a value.", "[uni TEST_CASE("unique_handle should be explicitly null constructible by nullhandle.", "[unique_handle]") { - constexpr unique_handle handle{ nullhandle }; + constexpr test_handle handle{ nullhandle }; STATIC_REQUIRE(!handle.is_valid()); STATIC_REQUIRE(!handle); @@ -40,10 +56,11 @@ TEST_CASE("unique_handle should be explicitly null constructible by nullhandle." TEST_CASE("unique_handle should be assignable by nullhandle.", "[unique_handle]") { - SL_UNIQUE_HANDLE_FULL_CONSTEXPR const unique_handle handle = [] + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const test_handle handle = [] { // ReSharper disable once CppInitializedValueIsAlwaysRewritten - unique_handle temp{}; + test_handle temp{}; temp = nullhandle; return temp; }(); @@ -54,7 +71,7 @@ TEST_CASE("unique_handle should be assignable by nullhandle.", "[unique_handle]" TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") { - constexpr unique_handle handle{ 42 }; + constexpr test_handle handle{ 42 }; STATIC_REQUIRE(handle.is_valid()); STATIC_REQUIRE(handle); @@ -62,21 +79,106 @@ TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") { - constexpr unique_handle handle = [] + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + test_handle handle = [] { // ReSharper disable once CppInitializedValueIsAlwaysRewritten - unique_handle temp{}; + test_handle temp{}; temp = 42; return temp; }(); - STATIC_REQUIRE(handle.is_valid()); - STATIC_REQUIRE(handle); + REQUIRE(handle.is_valid()); + REQUIRE(handle); +} + +#pragma warning(disable: 26444) +TEMPLATE_TEST_CASE_SIG +( + "delete action on assignment must only be invoked if unique_handle holds a value.", + "[unique_handle]", + ((auto VInit, auto VAssign, bool VExpected), VInit, VAssign, VExpected), + (42, 1337, true), + (42, nullhandle, true), + (nullhandle, 1337, false), + (nullhandle, nullhandle, false) +) +#pragma warning(disable: 26444) +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + int counter{}; + test_handle temp{ VInit }; + temp.delete_action() = delete_action_mock{ .invoke_counter = &counter }; + temp = VAssign; + return counter == 1; + }(); + + REQUIRE(result == VExpected); +} + +TEST_CASE("unique_handle should be move constructible and invalidate the source.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + // ReSharper disable once CppInitializedValueIsAlwaysRewritten + test_handle source{ 42 }; + test_handle target{ std::move(source) }; + return !source.is_valid() && target.is_valid(); + }(); + + REQUIRE(result); +} + +TEST_CASE("unique_handle should be move constructible and receive the value of other.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + // ReSharper disable once CppInitializedValueIsAlwaysRewritten + test_handle source{ 42 }; + const test_handle target{ std::move(source) }; + return target.raw() == 42; + }(); + + REQUIRE(result); +} + +TEST_CASE("unique_handle should be move assignable and invalidate the source.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + test_handle source{ 42 }; + // ReSharper disable once CppInitializedValueIsAlwaysRewritten + test_handle target{ nullhandle }; + target = std::move(source); + return !source.is_valid() && target.is_valid(); + }(); + + REQUIRE(result); +} + +TEST_CASE("unique_handle should be move assignable and receive the value of other.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + test_handle source{ 1337 }; + // ReSharper disable once CppInitializedValueIsAlwaysRewritten + test_handle target{ nullhandle }; + target = std::move(source); + return target.raw() == 1337; + }(); + + REQUIRE(result); } TEST_CASE("unique_handle::raw should expose a const reference of its value.", "[unique_handle]") { - constexpr unique_handle handle{ 1337 }; + constexpr test_handle handle{ 1337 }; constexpr const int& ref{ handle.raw() }; @@ -85,14 +187,14 @@ TEST_CASE("unique_handle::raw should expose a const reference of its value.", "[ TEST_CASE("unique_handle::raw should throw bad_handle_access if no value is hold.", "[unique_handle]") { - constexpr unique_handle handle{}; + constexpr test_handle handle{}; REQUIRE_THROWS_AS(handle.raw(), bad_handle_access); } TEST_CASE("unique_handle's operator * should expose a const reference of its value.", "[unique_handle]") { - constexpr unique_handle handle{ 42 }; + constexpr test_handle handle{ 42 }; constexpr const int& ref{ *handle }; @@ -101,7 +203,7 @@ TEST_CASE("unique_handle's operator * should expose a const reference of its val TEST_CASE("unique_handle's operator -> const overload should expose a const pointer to its value.", "[unique_handle]") { - constexpr auto access_via_ptr = [] class THandle>() + constexpr auto access_via_ptr = [] class THandle>() { THandle handle{ 1337 }; auto* ptr = handle.operator ->(); @@ -118,12 +220,84 @@ TEST_CASE("unique_handle's operator -> const overload should expose a const poin TEST_CASE("unique_handle::reset should reset to a nullhandle.", "[unique_handle]") { SL_UNIQUE_HANDLE_FULL_CONSTEXPR - const unique_handle handle = [] + const test_handle handle = [] { - unique_handle temp{ 42 }; + test_handle temp{ 42 }; temp.reset(); return temp; }(); REQUIRE(!handle); } + +TEST_CASE("resetting a handle without value should do nothing.", "[unique_handle]") +{ + REQUIRE_NOTHROW + ( + [] + { + test_handle temp{ nullhandle }; + temp.reset(); + }() + ); +} + +#pragma warning(disable: 26444) +TEMPLATE_TEST_CASE_SIG +( + "delete action on reset must only be invoked if unique_handle holds a value.", + "[unique_handle]", + ((auto VInit, bool VExpected), VInit, VExpected), + (42, true), + (nullhandle, false) +) +#pragma warning(disable: 26444) +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + int counter{}; + test_handle temp{ VInit }; + temp.delete_action() = delete_action_mock{ .invoke_counter = &counter }; + temp.reset(); + return counter == 1; + }(); + + REQUIRE(result == VExpected); +} + +TEST_CASE("unique_handle::delete_action should return a reference to the used deleter action object.", "[unique_handle]") +{ + constexpr bool result = [] + { + test_handle temp{ 42 }; + + return &temp.delete_action() == &std::as_const(temp).delete_action(); + }(); + + STATIC_REQUIRE(result); +} + +#pragma warning(disable: 26444) +TEMPLATE_TEST_CASE_SIG +( + "delete action on destruction must only be invoked if unique_handle holds a value.", + "[unique_handle]", + ((auto VInit, bool VExpected), VInit, VExpected), + (42, true), + (nullhandle, false) +) +#pragma warning(disable: 26444) +{ + constexpr bool result = [] + { + int counter{}; + { + test_handle temp{ VInit }; + temp.delete_action() = delete_action_mock{ .invoke_counter = &counter }; + } + return counter == 1; + }(); + + REQUIRE(result == VExpected); +} From d2047916bedcf2776b53c2beb485b50095f2eff3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 00:54:28 +0100 Subject: [PATCH 21/59] add&test: swap of unique_handle Signed-off-by: DNKpp --- include/unique_handle.hpp | 15 +++++++++++++++ tests/unique_handle.cpp | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index d47a3955a..0330accd5 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -74,6 +74,21 @@ namespace sl return *this; } + constexpr void swap + ( + unique_handle& other + ) + noexcept(noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_swappable_v + && std::is_nothrow_move_constructible_v + && std::is_nothrow_swappable_v)) + { + using std::swap; + + swap(m_Value, other.m_Value); + swap(m_DeleteAction, other.m_DeleteAction); + } + unique_handle(const unique_handle&) = delete; unique_handle& operator =(const unique_handle&) = delete; diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 94e1fdfea..1095549b5 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -176,6 +176,20 @@ TEST_CASE("unique_handle should be move assignable and receive the value of othe REQUIRE(result); } +TEST_CASE("unique_handle should be swapable.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + test_handle lhs{ 1337 }; + test_handle rhs{ 42 }; + lhs.swap(rhs); + return *lhs == 42 && *rhs == 1337; + }(); + + REQUIRE(result); +} + TEST_CASE("unique_handle::raw should expose a const reference of its value.", "[unique_handle]") { constexpr test_handle handle{ 1337 }; From 52efa8b598d9fb38f482a2ed8849278b520bb42d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:02:19 +0100 Subject: [PATCH 22/59] add: make unique_handle move ctor and assign conditionally noexcept Signed-off-by: DNKpp --- include/unique_handle.hpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 0330accd5..13f6cc4ae 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -55,14 +55,24 @@ namespace sl invoke_delete_action_if_necessary(); } - SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle(unique_handle&& other) noexcept + SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle + ( + unique_handle&& other + ) noexcept(noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_copy_constructible_v)) : m_Value{ std::exchange(other.m_Value, std::nullopt) }, m_DeleteAction{ other.m_DeleteAction } { other.m_Value.reset(); } - SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator =(unique_handle&& other) noexcept + SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator = + ( + unique_handle&& other + ) noexcept(noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_move_assignable_v + && std::is_nothrow_copy_constructible_v + && std::is_nothrow_copy_assignable_v)) { if (this != &other) { @@ -77,11 +87,10 @@ namespace sl constexpr void swap ( unique_handle& other - ) - noexcept(noexcept(std::is_nothrow_move_constructible_v - && std::is_nothrow_swappable_v - && std::is_nothrow_move_constructible_v - && std::is_nothrow_swappable_v)) + ) noexcept(noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_swappable_v + && std::is_nothrow_move_constructible_v + && std::is_nothrow_swappable_v)) { using std::swap; From 3473e5de62012fd3a8f0097cb688dd830c0fa27a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:06:23 +0100 Subject: [PATCH 23/59] test: add self move test case for unique_handle Signed-off-by: DNKpp --- tests/unique_handle.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 1095549b5..5d67b57a4 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -176,6 +176,19 @@ TEST_CASE("unique_handle should be move assignable and receive the value of othe REQUIRE(result); } +TEST_CASE("moving unique_handle with itself should change nothing.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + test_handle handle{ 1337 }; + handle = std::move(handle); + return handle.is_valid() && *handle == 1337; + }(); + + REQUIRE(result); +} + TEST_CASE("unique_handle should be swapable.", "[unique_handle]") { SL_UNIQUE_HANDLE_FULL_CONSTEXPR From a61c47d1200c42b14de3b68289debf17edff5433 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:07:29 +0100 Subject: [PATCH 24/59] test: add self swap test case for unique_handle Signed-off-by: DNKpp --- tests/unique_handle.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 5d67b57a4..ac9747d75 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -189,6 +189,19 @@ TEST_CASE("moving unique_handle with itself should change nothing.", "[unique_ha REQUIRE(result); } +TEST_CASE("swapping unique_handle with itself should change nothing.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + test_handle handle{ 1337 }; + handle.swap(handle); + return handle.is_valid() && *handle == 1337; + }(); + + REQUIRE(result); +} + TEST_CASE("unique_handle should be swapable.", "[unique_handle]") { SL_UNIQUE_HANDLE_FULL_CONSTEXPR From 77f0f81fc417f6e3a31e252fb971cdb1f226a678 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:09:41 +0100 Subject: [PATCH 25/59] refactor: apply code formatting Signed-off-by: DNKpp --- include/unique_handle.hpp | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 13f6cc4ae..14a5b0a42 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -136,28 +136,16 @@ namespace sl } [[nodiscard]] - constexpr const T& raw() const - { - return m_Value.value(); - } + constexpr const T& raw() const { return m_Value.value(); } [[nodiscard]] - constexpr const T& operator *() const - { - return *m_Value; - } + constexpr const T& operator *() const { return *m_Value; } [[nodiscard]] - constexpr T* operator ->() - { - return &*m_Value; - } + constexpr T* operator ->() { return &*m_Value; } [[nodiscard]] - constexpr const T* operator ->() const - { - return &*m_Value; - } + constexpr const T* operator ->() const { return &*m_Value; } [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } @@ -174,16 +162,10 @@ namespace sl } [[nodiscard]] - constexpr delete_action_type& delete_action() noexcept - { - return m_DeleteAction; - } + constexpr delete_action_type& delete_action() noexcept { return m_DeleteAction; } [[nodiscard]] - constexpr const delete_action_type& delete_action() const noexcept - { - return m_DeleteAction; - } + constexpr const delete_action_type& delete_action() const noexcept { return m_DeleteAction; } private: std::optional m_Value{}; From 1537b531f9240288c6c8714b2a3bb07559980f1f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:10:52 +0100 Subject: [PATCH 26/59] fix: remove wrong nodiscard attribute Signed-off-by: DNKpp --- include/unique_handle.hpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 14a5b0a42..4836ae598 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -135,6 +135,13 @@ namespace sl return *this; } + SL_UNIQUE_HANDLE_FULL_CONSTEXPR void reset() noexcept + { + invoke_delete_action_if_necessary(); + + m_Value.reset(); + } + [[nodiscard]] constexpr const T& raw() const { return m_Value.value(); } @@ -153,14 +160,6 @@ namespace sl [[nodiscard]] constexpr bool is_valid() const noexcept { return m_Value.has_value(); } - [[nodiscard]] - SL_UNIQUE_HANDLE_FULL_CONSTEXPR void reset() noexcept - { - invoke_delete_action_if_necessary(); - - m_Value.reset(); - } - [[nodiscard]] constexpr delete_action_type& delete_action() noexcept { return m_DeleteAction; } From 0d92c553cf36a5389e85ecda9f90ce223cf463f5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:16:20 +0100 Subject: [PATCH 27/59] fix: add missing noexcept declarations Signed-off-by: DNKpp --- include/unique_handle.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/unique_handle.hpp b/include/unique_handle.hpp index 4836ae598..922959334 100644 --- a/include/unique_handle.hpp +++ b/include/unique_handle.hpp @@ -146,13 +146,13 @@ namespace sl constexpr const T& raw() const { return m_Value.value(); } [[nodiscard]] - constexpr const T& operator *() const { return *m_Value; } + constexpr const T& operator *() const noexcept { return *m_Value; } [[nodiscard]] - constexpr T* operator ->() { return &*m_Value; } + constexpr T* operator ->() noexcept { return &*m_Value; } [[nodiscard]] - constexpr const T* operator ->() const { return &*m_Value; } + constexpr const T* operator ->() const noexcept { return &*m_Value; } [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } From e32a099f29a9480375bf1d571e47d23525ab7240 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:28:16 +0100 Subject: [PATCH 28/59] fix: move unique_handle into correct subfolder Signed-off-by: DNKpp --- include/{ => Simple-Utility}/unique_handle.hpp | 2 +- tests/unique_handle.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename include/{ => Simple-Utility}/unique_handle.hpp (98%) diff --git a/include/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp similarity index 98% rename from include/unique_handle.hpp rename to include/Simple-Utility/unique_handle.hpp index 922959334..c7318bb63 100644 --- a/include/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -11,7 +11,7 @@ #include #include -#include "Simple-Utility/concepts/stl_counterparts.hpp" +#include "concepts/stl_counterparts.hpp" // some of the std::optional interface hasn't declared constexpr before #if __cpp_lib_optional >= 202106 diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index ac9747d75..0bf10bdd5 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -7,7 +7,7 @@ #include "helper.hpp" -#include "unique_handle.hpp" +#include "Simple-Utility/unique_handle.hpp" using namespace sl; From fb5f9836bc3aeefe3750040a9c176e125c4a412b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 01:32:00 +0100 Subject: [PATCH 29/59] fix: wrong include paths Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index c7318bb63..922959334 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -11,7 +11,7 @@ #include #include -#include "concepts/stl_counterparts.hpp" +#include "Simple-Utility/concepts/stl_counterparts.hpp" // some of the std::optional interface hasn't declared constexpr before #if __cpp_lib_optional >= 202106 From 3ffebf36d71d657bd898e9dce3ec6fe58d3d997f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 02:08:47 +0100 Subject: [PATCH 30/59] fix: please gcc in test case code Signed-off-by: DNKpp --- tests/unique_handle.cpp | 44 ++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 0bf10bdd5..ff19c0afa 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -218,11 +218,14 @@ TEST_CASE("unique_handle should be swapable.", "[unique_handle]") TEST_CASE("unique_handle::raw should expose a const reference of its value.", "[unique_handle]") { - constexpr test_handle handle{ 1337 }; - - constexpr const int& ref{ handle.raw() }; + constexpr bool result = [] + { + test_handle handle{ 1337 }; + const int& ref{ handle.raw() }; + return ref == 1337; + }(); - STATIC_REQUIRE(ref == 1337); + STATIC_REQUIRE(result); } TEST_CASE("unique_handle::raw should throw bad_handle_access if no value is hold.", "[unique_handle]") @@ -234,27 +237,32 @@ TEST_CASE("unique_handle::raw should throw bad_handle_access if no value is hold TEST_CASE("unique_handle's operator * should expose a const reference of its value.", "[unique_handle]") { - constexpr test_handle handle{ 42 }; - - constexpr const int& ref{ *handle }; + constexpr bool result = [] + { + test_handle handle{ 42 }; + const int& ref{ *handle }; + return ref == 42; + }(); - STATIC_REQUIRE(ref == 42); + REQUIRE(result); } -TEST_CASE("unique_handle's operator -> const overload should expose a const pointer to its value.", "[unique_handle]") +TEMPLATE_TEST_CASE +( + "unique_handle's operator -> overload should expose a pointer to its value.", + "[unique_handle]", + test_handle, + const test_handle +) { - constexpr auto access_via_ptr = [] class THandle>() + constexpr bool result = [] { - THandle handle{ 1337 }; + TestType handle{ 1337 }; auto* ptr = handle.operator ->(); - return *ptr; - }; - - constexpr int non_const_result = access_via_ptr.operator()(); - constexpr int const_result = access_via_ptr.operator()(); + return *ptr == 1337; + }(); - STATIC_REQUIRE(non_const_result == 1337); - STATIC_REQUIRE(const_result == 1337); + STATIC_REQUIRE(result); } TEST_CASE("unique_handle::reset should reset to a nullhandle.", "[unique_handle]") From 2851a6431bddf7f29d6062a30ba6ff7984e42fff Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 19:11:52 +0100 Subject: [PATCH 31/59] add&test: in-place ctor and emplace function of unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 14 ++++++++ tests/unique_handle.cpp | 41 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 922959334..56c338b38 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -135,6 +135,20 @@ namespace sl return *this; } + template + requires std::constructible_from + constexpr explicit unique_handle(std::in_place_t, TArgs&&... args) + : m_Value{ std::in_place, std::forward(args)... } + { + } + + template + requires std::constructible_from + SL_UNIQUE_HANDLE_FULL_CONSTEXPR T& emplace(TArgs&&... args) + { + return m_Value.emplace(std::forward(args)...); + } + SL_UNIQUE_HANDLE_FULL_CONSTEXPR void reset() noexcept { invoke_delete_action_if_necessary(); diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index ff19c0afa..cf69c91ee 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -13,6 +13,26 @@ using namespace sl; namespace { + struct value_t + { + int a{}; + int z{}; + + constexpr value_t(int a) + : a{ a } + { + } + + constexpr value_t(int a, int z) + : a{ a }, + z{ z } + { + } + + constexpr value_t(value_t&&) noexcept = default; + constexpr value_t& operator =(value_t&&) noexcept = default; + }; + struct delete_action_mock { int* invoke_counter{}; @@ -77,6 +97,27 @@ TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") STATIC_REQUIRE(handle); } +TEST_CASE("unique_handle should be explicitly in-place construct value when std::in_place token is used.", "[unique_handle]") +{ + constexpr unique_handle handle{ std::in_place, 42, 1337 }; + + STATIC_REQUIRE(handle->a == 42); + STATIC_REQUIRE(handle->z == 1337); +} + +TEST_CASE("unique_handle::emplace constructs value in place.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + unique_handle handle{}; + handle.emplace(1337, 42); + return handle->a == 1337 && handle->z == 42; + }(); + + REQUIRE(result); +} + TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") { SL_UNIQUE_HANDLE_FULL_CONSTEXPR From 10eb629ffcf86ae87d95c13a05c637528a54f66e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 19:15:51 +0100 Subject: [PATCH 32/59] test: some slight fixes Signed-off-by: DNKpp --- tests/unique_handle.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index cf69c91ee..d09af16e8 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -121,7 +121,7 @@ TEST_CASE("unique_handle::emplace constructs value in place.", "[unique_handle]" TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") { SL_UNIQUE_HANDLE_FULL_CONSTEXPR - test_handle handle = [] + const test_handle handle = [] { // ReSharper disable once CppInitializedValueIsAlwaysRewritten test_handle temp{}; @@ -164,9 +164,8 @@ TEST_CASE("unique_handle should be move constructible and invalidate the source. SL_UNIQUE_HANDLE_FULL_CONSTEXPR const bool result = [] { - // ReSharper disable once CppInitializedValueIsAlwaysRewritten test_handle source{ 42 }; - test_handle target{ std::move(source) }; + const test_handle target{ std::move(source) }; return !source.is_valid() && target.is_valid(); }(); From e6c97842e52780e774b67207055fb0112341747c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 19:38:48 +0100 Subject: [PATCH 33/59] add&test: constructor overloads for explicit delete action initialization Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 19 +++++- tests/unique_handle.cpp | 73 +++++++++++++++++++----- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 56c338b38..84f6f951b 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -114,12 +114,19 @@ namespace sl return *this; } + constexpr unique_handle(const delete_action_type& deleteAction) noexcept + : m_Value{ std::nullopt }, + m_DeleteAction{ deleteAction } + { + } + template T2> requires concepts::not_same_as, unique_handle> && concepts::not_same_as, nullhandle_t> explicit (!std::convertible_to) - constexpr unique_handle(T2&& value) - : m_Value{ std::forward(value) } + constexpr unique_handle(T2&& value, const delete_action_type& deleteAction = delete_action_type{}) + : m_Value{ std::forward(value) }, + m_DeleteAction{ deleteAction } { } @@ -142,6 +149,14 @@ namespace sl { } + template + requires std::constructible_from + constexpr explicit unique_handle(std::in_place_t, const delete_action_type& deleteAction, TArgs&&... args) + : m_Value{ std::in_place, std::forward(args)... }, + m_DeleteAction{ deleteAction } + { + } + template requires std::constructible_from SL_UNIQUE_HANDLE_FULL_CONSTEXPR T& emplace(TArgs&&... args) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index d09af16e8..7ff63ed62 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -89,30 +89,35 @@ TEST_CASE("unique_handle should be assignable by nullhandle.", "[unique_handle]" REQUIRE(!handle); } -TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") +TEST_CASE("unique_handle should be empty constructible by deleteAction.", "[unique_handle]") { - constexpr test_handle handle{ 42 }; + constexpr bool result = [] + { + int testValue = 1337; + const test_handle handle{ delete_action_mock{ &testValue } }; - STATIC_REQUIRE(handle.is_valid()); - STATIC_REQUIRE(handle); + return !handle && *handle.delete_action().invoke_counter == 1337; + }(); + + REQUIRE(result); } -TEST_CASE("unique_handle should be explicitly in-place construct value when std::in_place token is used.", "[unique_handle]") +TEST_CASE("unique_handle should be constructible by value.", "[unique_handle]") { - constexpr unique_handle handle{ std::in_place, 42, 1337 }; + constexpr test_handle handle{ 42 }; - STATIC_REQUIRE(handle->a == 42); - STATIC_REQUIRE(handle->z == 1337); + STATIC_REQUIRE(handle.is_valid()); + STATIC_REQUIRE(handle); } -TEST_CASE("unique_handle::emplace constructs value in place.", "[unique_handle]") +TEST_CASE("unique_handle should be constructible by value and deleteAction.", "[unique_handle]") { - SL_UNIQUE_HANDLE_FULL_CONSTEXPR - const bool result = [] + constexpr bool result = [] { - unique_handle handle{}; - handle.emplace(1337, 42); - return handle->a == 1337 && handle->z == 42; + int testValue = 1337; + const test_handle handle{ 42, delete_action_mock{ &testValue } }; + + return *handle == 42 && *handle.delete_action().invoke_counter == 1337; }(); REQUIRE(result); @@ -133,6 +138,46 @@ TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") REQUIRE(handle); } +TEST_CASE("unique_handle should be explicitly in-place construct value when std::in_place token is used.", "[unique_handle]") +{ + constexpr unique_handle handle{ std::in_place, 42, 1337 }; + + STATIC_REQUIRE(handle->a == 42); + STATIC_REQUIRE(handle->z == 1337); +} + +TEST_CASE +( + "unique_handle should be explicitly in-place construct value and deleteAction when std::in_place token is used.", + "[unique_handle]" +) +{ + constexpr bool result = [] + { + int testValue = 1337; + const unique_handle handle{ std::in_place, delete_action_mock{ &testValue }, 42, -1 }; + + return handle->a == 42 + && handle->z == -1 + && *handle.delete_action().invoke_counter == 1337; + }(); + + REQUIRE(result); +} + +TEST_CASE("unique_handle::emplace constructs value in place.", "[unique_handle]") +{ + SL_UNIQUE_HANDLE_FULL_CONSTEXPR + const bool result = [] + { + unique_handle handle{}; + handle.emplace(1337, 42); + return handle->a == 1337 && handle->z == 42; + }(); + + REQUIRE(result); +} + #pragma warning(disable: 26444) TEMPLATE_TEST_CASE_SIG ( From 7ab1cbdfa76593794df7c4f24f0296aa3fcd1e1f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 19:42:59 +0100 Subject: [PATCH 34/59] remove non-const delete_action from unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 8 +++----- tests/unique_handle.cpp | 22 ++++++++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 84f6f951b..df63c2643 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -101,8 +101,9 @@ namespace sl unique_handle(const unique_handle&) = delete; unique_handle& operator =(const unique_handle&) = delete; - constexpr unique_handle(nullhandle_t) noexcept - : m_Value{ std::nullopt } + constexpr unique_handle(nullhandle_t, const delete_action_type& deleteAction = delete_action_type()) noexcept + : m_Value{ std::nullopt }, + m_DeleteAction{ deleteAction } { } @@ -189,9 +190,6 @@ namespace sl [[nodiscard]] constexpr bool is_valid() const noexcept { return m_Value.has_value(); } - [[nodiscard]] - constexpr delete_action_type& delete_action() noexcept { return m_DeleteAction; } - [[nodiscard]] constexpr const delete_action_type& delete_action() const noexcept { return m_DeleteAction; } diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 7ff63ed62..ed3efeb6b 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -74,6 +74,19 @@ TEST_CASE("unique_handle should be explicitly null constructible by nullhandle." STATIC_REQUIRE(!handle); } +TEST_CASE("unique_handle should be empty constructible by nullhandle and deleteAction.", "[unique_handle]") +{ + constexpr bool result = [] + { + int testValue = 1337; + const test_handle handle{ nullhandle, delete_action_mock{ &testValue } }; + + return !handle && *handle.delete_action().invoke_counter == 1337; + }(); + + REQUIRE(result); +} + TEST_CASE("unique_handle should be assignable by nullhandle.", "[unique_handle]") { SL_UNIQUE_HANDLE_FULL_CONSTEXPR @@ -195,8 +208,7 @@ TEMPLATE_TEST_CASE_SIG const bool result = [] { int counter{}; - test_handle temp{ VInit }; - temp.delete_action() = delete_action_mock{ .invoke_counter = &counter }; + test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; temp = VAssign; return counter == 1; }(); @@ -390,8 +402,7 @@ TEMPLATE_TEST_CASE_SIG const bool result = [] { int counter{}; - test_handle temp{ VInit }; - temp.delete_action() = delete_action_mock{ .invoke_counter = &counter }; + test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; temp.reset(); return counter == 1; }(); @@ -426,8 +437,7 @@ TEMPLATE_TEST_CASE_SIG { int counter{}; { - test_handle temp{ VInit }; - temp.delete_action() = delete_action_mock{ .invoke_counter = &counter }; + const test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; } return counter == 1; }(); From d25fa04ba2462b5620aaf50e0220a83c02997f00 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 20:45:22 +0100 Subject: [PATCH 35/59] add&test: various three-way-comparisons overloads for unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 33 ++++++++++++++++ tests/unique_handle.cpp | 48 +++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index df63c2643..21a046036 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -205,6 +205,39 @@ namespace sl std::invoke(m_DeleteAction, *m_Value); } }; + + template + [[nodiscard]] + constexpr std::compare_three_way_result_t operator <=> + ( + const unique_handle& lhs, + const unique_handle& rhs + ) + { + if (lhs && rhs) + { + return *lhs <=> *rhs; + } + return lhs.is_valid() <=> rhs.is_valid(); + } + + template + [[nodiscard]] + constexpr std::compare_three_way_result_t operator <=>(const unique_handle& lhs, const T& rhs) + { + if (lhs) + { + return *lhs <=> rhs; + } + return std::compare_three_way_result_t::less; + } + + template + [[nodiscard]] + constexpr std::strong_ordering operator <=>(const unique_handle& lhs, nullhandle_t) noexcept + { + return lhs.is_valid() <=> false; + } } #endif diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index ed3efeb6b..64ba12f63 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -437,10 +437,56 @@ TEMPLATE_TEST_CASE_SIG { int counter{}; { - const test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; + const test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; } return counter == 1; }(); REQUIRE(result == VExpected); } + +#pragma warning(disable: 26444) +TEMPLATE_TEST_CASE_SIG +( + "unique_handle should be three-way-comparable with unqiue_handle of same type.", + "[unique_handle]", + ((auto VLhs, auto VRhs, auto VResult), VLhs, VRhs, VResult), + (nullhandle, nullhandle, std::strong_ordering::equal), + (nullhandle, 42, std::strong_ordering::less), + (42, nullhandle, std::strong_ordering::greater), + (42, 1337, std::strong_ordering::less) +) +#pragma warning(disable: 26444) +{ + constexpr auto result = [] + { + const test_handle lhs{ VLhs }; + const test_handle rhs{ VRhs }; + + return lhs <=> rhs; + }(); + + STATIC_REQUIRE(result == VResult); +} + +TEST_CASE("unique_handle should be three-way-comparable with nullhandle.", "[unique_handle]") +{ + STATIC_REQUIRE((nullhandle <=> test_handle{ 42 }) == std::strong_ordering::less); + STATIC_REQUIRE((nullhandle <=> test_handle{}) == std::strong_ordering::equal); + STATIC_REQUIRE((test_handle{ 42 } <=> nullhandle) == std::strong_ordering::greater); + STATIC_REQUIRE((test_handle{} <=> nullhandle) == std::strong_ordering::equal); +} + +TEST_CASE("unique_handle should be three-way-comparable with value type.", "[unique_handle]") +{ + STATIC_REQUIRE((42 <=> test_handle{ 1337 }) == std::strong_ordering::less); + STATIC_REQUIRE((1337 <=> test_handle{ 42 }) == std::strong_ordering::greater); + STATIC_REQUIRE((test_handle{ 1337 } <=> 42 ) == std::strong_ordering::greater); + STATIC_REQUIRE((test_handle{ 42 } <=> 1337) == std::strong_ordering::less); + + STATIC_REQUIRE((1337 <=> test_handle{}) == std::strong_ordering::greater); + STATIC_REQUIRE((test_handle{} <=> 1337) == std::strong_ordering::less); + + STATIC_REQUIRE((42 <=> test_handle{ 42 }) == std::strong_ordering::equal); + STATIC_REQUIRE((test_handle{ 42 } <=> 42) == std::strong_ordering::equal); +} From d639ea9e849f0a7948e88eb408f9ea5d7ce7f935 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 20:50:50 +0100 Subject: [PATCH 36/59] fix: relax the constraint for unique_handle <=> value_type Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 21a046036..bd658eda2 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -221,9 +221,9 @@ namespace sl return lhs.is_valid() <=> rhs.is_valid(); } - template + template T2> [[nodiscard]] - constexpr std::compare_three_way_result_t operator <=>(const unique_handle& lhs, const T& rhs) + constexpr std::compare_three_way_result_t operator <=>(const unique_handle& lhs, const T2& rhs) { if (lhs) { From 83b1457e61f57b795bbf6a6426e91ff791dff03b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 21:22:25 +0100 Subject: [PATCH 37/59] fix: add compare include Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index bd658eda2..8b8cec623 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -8,6 +8,7 @@ #pragma once +#include #include #include From 386e816f2257c6c009b0eb225ed6b07c0aa34322 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 21:22:33 +0100 Subject: [PATCH 38/59] fix: please gcc Signed-off-by: DNKpp --- tests/unique_handle.cpp | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 64ba12f63..66df019f4 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -445,28 +445,12 @@ TEMPLATE_TEST_CASE_SIG REQUIRE(result == VExpected); } -#pragma warning(disable: 26444) -TEMPLATE_TEST_CASE_SIG -( - "unique_handle should be three-way-comparable with unqiue_handle of same type.", - "[unique_handle]", - ((auto VLhs, auto VRhs, auto VResult), VLhs, VRhs, VResult), - (nullhandle, nullhandle, std::strong_ordering::equal), - (nullhandle, 42, std::strong_ordering::less), - (42, nullhandle, std::strong_ordering::greater), - (42, 1337, std::strong_ordering::less) -) -#pragma warning(disable: 26444) +TEST_CASE("unique_handle should be three-way-comparable with unqiue_handle of same type.", "[unique_handle]") { - constexpr auto result = [] - { - const test_handle lhs{ VLhs }; - const test_handle rhs{ VRhs }; - - return lhs <=> rhs; - }(); - - STATIC_REQUIRE(result == VResult); + STATIC_REQUIRE((test_handle{} <=> test_handle{}) == std::strong_ordering::equal); + STATIC_REQUIRE((test_handle{} <=> test_handle{ 42 }) == std::strong_ordering::less); + STATIC_REQUIRE((test_handle{42 } <=> test_handle{}) == std::strong_ordering::greater); + STATIC_REQUIRE((test_handle{42 } <=> test_handle{1337}) == std::strong_ordering::less); } TEST_CASE("unique_handle should be three-way-comparable with nullhandle.", "[unique_handle]") From 6d72507fbd7ab038d21b77a2380d0ed20ab4dfdf Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 29 Dec 2021 21:47:22 +0100 Subject: [PATCH 39/59] ci: adjust run_tests.yml Signed-off-by: DNKpp --- .github/workflows/run_tests.yml | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index b130621ab..09c63251d 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -29,47 +29,45 @@ jobs: sudo apt-get update sudo apt-get install ${{ matrix.compiler.pkg }} -y - name: Compile tests - working-directory: out env: CXX: ${{ matrix.compiler.exe }} run: | - cmake -DSIMPLE_UTILITY_BUILD_TESTS=ON -S .. - make -j4 + cmake -DSIMPLE_UTILITY_BUILD_TESTS=ON -B out -S . + cmake --build out -j4 - name: Run tests - working-directory: out/tests env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --timeout 30 -C Debug -j4 + run: ctest --test-dir out/tests --timeout 30 -C Debug -j4 windows: - runs-on: windows-latest + runs-on: windows-2022 timeout-minutes: 10 strategy: fail-fast: false matrix: - toolset: [v142, clang-cl] + toolset: [v142, v143, clang-cl] include: - toolset: v142 toolset_option: -T"v142" + - toolset: v143 + toolset_option: -T"v143" - toolset: clang-cl toolset_option: -T"ClangCl" steps: - uses: actions/checkout@v2 - name: Compile tests - working-directory: out run: | - cmake -DSIMPLE_UTILITY_BUILD_TESTS=ON ${{ matrix.toolset_option }} .. - cmake --build . -j 4 + cmake -DSIMPLE_UTILITY_BUILD_TESTS=ON ${{ matrix.toolset_option }} -B out -S . + cmake --build out -j4 - name: Run tests - working-directory: out/tests env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --timeout 30 -C Debug -j4 + run: ctest --test-dir out/tests --timeout 30 -C Debug -j4 # macos: -# runs-on: macOS-latest +# runs-on: macos-latest # timeout-minutes: 10 # # strategy: @@ -78,12 +76,11 @@ jobs: # steps: # - uses: actions/checkout@v2 # - name: Compile tests -# working-directory: out # run: | -# cmake -DSIMPLE_UTILITY_BUILD_TESTS=ON .. -# make -j4 +# cmake -DSIMPLE_UTILITY_BUILD_TESTS=ON -B out -S . +# cmake --build out -j4 # - name: Run tests # working-directory: out/tests # env: # CTEST_OUTPUT_ON_FAILURE: 1 -# run: ctest --timeout 30 -C Debug -j4 \ No newline at end of file +# run: ctest --test-dir out/tests --timeout 30 -C Debug -j4 From db74580dc23101bbb04bd7875bc3b3f4f758bbb8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 02:34:30 +0100 Subject: [PATCH 40/59] docs: start documenting unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 94 ++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 8b8cec623..a9a08af93 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -23,16 +23,34 @@ namespace sl { + /** + * \defgroup unique_handle unique_handle + * @{ + */ + + /** + * \brief helper type for indicating unique_handles with uninitialized state + */ // ReSharper disable once IdentifierTypo struct nullhandle_t { }; + /** + * \brief an object of type nullhandle_t + */ // ReSharper disable once IdentifierTypo constexpr nullhandle_t nullhandle{}; + /** + * \brief exception type which indicates checked access to an uninitialized value + */ using bad_handle_access = std::bad_optional_access; + /** + * \brief default delete action for unique_handle with an empty operator () + * \tparam T type to operate on + */ template struct default_delete_action { @@ -41,6 +59,65 @@ namespace sl } }; + /** + * \brief This type models some kind of ``std::optional`` behaviour but resets itself on move + * \details This type is in fact a wrapper around a ``std::optional``, thus has at least the overhead of that. Additionally it adds two + * important aspects: + * -# it resets its internal value after it got moved. + * -# it invokes its delete action every time when the internal value switches its state from initialized to uninitialized. + * + * The latter happens when the value is in an initialized state and the ``unique_handle`` gets + * - moved, + * - destructed or + * - assigned + * + * This behaviour is useful in cases when one has an identifier to a resource, which isn't stored on the heap (thus a ``std::unique_ptr`` is not + * a good option), and this identifier should have the responsibility as an owner over that resource, but the resource itself is not bound to the + * lifetime of that identifier. This might sound quite abstract, thus let us visit a simple example. + * + * Here some entities are stored in a simple ``std::list``. Imagine this entities are accessible from many places in your program, thus something + * like this can easily happen. + * ```cpp + * std::list entities{}; + * + * { + * entities.emplace_front(); + * auto entity_id = entities.begin(); + * + * // do some actions with entity and other stuff + * + * // entity should now be erased. Not actually c++-ig, is it? + * entities.erase(entity_id); + * } + * ``` + * This is clearly no ``memory leak`` but if one forgets to erase the entity, it exists until the list is cleared. + * With ``unique_handle`` one can do this. + * ```cpp + * struct list_delete_action + * { + * std::list* list{}; // pointer here, because a delete action must be move and copyable + * + * void operator ()(const std::list::iterator& itr) { list->erase(itr); } + * }; + * std::list entities{}; + * + * { + * entities.emplace_front(); + * sl::unique_handle entity_id{ entities.begin(), list_delete_action{ &entities } }; + * + * // do some actions with entity and other stuff + * + * // no cleanup necessary + * } + * ``` + * Of course, at a first glance this is quite more verbose, but in the long term nobody has to care about that entity anymore. This is what ``RAII`` is about. + * Note that ``unique_handles`` also can be stored as a member, then they really begin to shine, because if one would like to bind that entity to the lifetime of + * an other object that would of course lead to custom move constructor, assignment operator and destructor and explicitly deleted copy. With a + * ``unique_handle`` none of this is necessary (and this is in fact the main reason why I decided to implement this). + * + * \tparam T The type of the stored value + * \tparam TDeleteAction Type of the used delete action + */ template TDeleteAction = default_delete_action> requires std::copyable class unique_handle @@ -49,13 +126,23 @@ namespace sl using element_type = T; using delete_action_type = TDeleteAction; + /** + * \brief Default constructor. The value will be in an uninitialized stated and the delete action gets default constructed. + */ constexpr unique_handle() noexcept = default; + /** + * \brief Destruct. Does invoke the delete action if the value is in an initialized state. + */ constexpr ~unique_handle() noexcept { invoke_delete_action_if_necessary(); } + /** + * \brief Move constructor, which relocates the ownership of the value to the target and resets the source. Delete actions will be copied. + * \param other The source object which will lose ownership, if it has any. + */ SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle ( unique_handle&& other @@ -67,6 +154,11 @@ namespace sl other.m_Value.reset(); } + /** + * \brief Move assignment, which relocates the ownership of the value to the target and resets the source. Delete actions will be copied. + * \param other The source object which will lose ownership, if it has any. + * \return A reference to the target object + */ SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator = ( unique_handle&& other @@ -239,6 +331,8 @@ namespace sl { return lhs.is_valid() <=> false; } + + /** @} */ } #endif From 3afcf89fc11efb8d4d5ecd22c6188153dccd3cda Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 02:34:59 +0100 Subject: [PATCH 41/59] fix: remove redundant reset of other in move ctor of unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index a9a08af93..3e02c5de8 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -151,7 +151,6 @@ namespace sl : m_Value{ std::exchange(other.m_Value, std::nullopt) }, m_DeleteAction{ other.m_DeleteAction } { - other.m_Value.reset(); } /** From f06b6b2d0adc0d6e95d29bae5cdd902b24af4ec6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 02:36:14 +0100 Subject: [PATCH 42/59] remove CMakeSettings.json Signed-off-by: DNKpp --- .gitignore | 3 ++- CMakeSettings.json | 22 ---------------------- 2 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 CMakeSettings.json diff --git a/.gitignore b/.gitignore index f739b1b11..feb1f8a73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vs/ -Folder.DotSettings* \ No newline at end of file +Folder.DotSettings* +CMakeSettings.json \ No newline at end of file diff --git a/CMakeSettings.json b/CMakeSettings.json deleted file mode 100644 index 09df61dc8..000000000 --- a/CMakeSettings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "variables": [ - { - "name": "SIMPLE_UTILITY_BUILD_TESTS", - "value": "True", - "type": "BOOL" - } - ] - } - ] -} \ No newline at end of file From ba8c9fe78329e9d6ad6c4d8a38ffb258eb633864 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 02:50:12 +0100 Subject: [PATCH 43/59] docs: extend unique_handle docs Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 54 +++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 3e02c5de8..ea5380af3 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -155,7 +155,7 @@ namespace sl /** * \brief Move assignment, which relocates the ownership of the value to the target and resets the source. Delete actions will be copied. - * \param other The source object which will lose ownership, if it has any. + * \param other The source object which will lose ownership, if it has any. * \return A reference to the target object */ SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator = @@ -176,6 +176,10 @@ namespace sl return *this; } + /** + * \brief Swaps the target and the source in a more efficient way. + * \param other The source object + */ constexpr void swap ( unique_handle& other @@ -190,15 +194,30 @@ namespace sl swap(m_DeleteAction, other.m_DeleteAction); } + /** + * \brief Explicitly deleted copy constructor. + */ unique_handle(const unique_handle&) = delete; + /** + * \brief Explicitly deleted copy assignment. + * \return nothing + */ unique_handle& operator =(const unique_handle&) = delete; + /** + * \brief Explicitly does not initialize the value. This overload is mainly used for convenience when returning + * a nullhandle. + * \param deleteAction The provided delete action object + */ constexpr unique_handle(nullhandle_t, const delete_action_type& deleteAction = delete_action_type()) noexcept : m_Value{ std::nullopt }, m_DeleteAction{ deleteAction } { } + /** + * \brief Explicitly resets the value and invokes the delete action if value was initialized. + */ SL_UNIQUE_HANDLE_FULL_CONSTEXPR unique_handle& operator =(nullhandle_t) noexcept { invoke_delete_action_if_necessary(); @@ -207,12 +226,22 @@ namespace sl return *this; } + /** + * \brief Constructor overload for explicitly providing a delete action. + * \param deleteAction The provided delete action object + */ constexpr unique_handle(const delete_action_type& deleteAction) noexcept : m_Value{ std::nullopt }, m_DeleteAction{ deleteAction } { } + /** + * \brief Constructor overload for initializing the value. + * \tparam T2 Type of the provided value. Must be convertible to ``T``. + * \param value Used object to initialize the value + * \param deleteAction The provided delete action object + */ template T2> requires concepts::not_same_as, unique_handle> && concepts::not_same_as, nullhandle_t> @@ -223,6 +252,12 @@ namespace sl { } + /** + * \brief Assignment operator overload for assigning the value. + * \tparam T2 Type of the provided value. Must be convertible to ``T``. + * \param value Used object to assign the value + * \return A reference to this + */ template T2> requires concepts::not_same_as, unique_handle> && concepts::not_same_as, nullhandle_t> @@ -235,6 +270,11 @@ namespace sl return *this; } + /** + * \brief Constructor overload for directly initializing the value with a set of arguments. + * \tparam TArgs Type of the provided arguments. + * \param args Used arguments to initialize the value + */ template requires std::constructible_from constexpr explicit unique_handle(std::in_place_t, TArgs&&... args) @@ -242,6 +282,13 @@ namespace sl { } + /** + * \brief Constructor overload for directly initializing the value with a set of arguments and also initializing + * the delete action. + * \tparam TArgs Type of the provided arguments. + * \param deleteAction The provided delete action object + * \param args Used arguments to initialize the value + */ template requires std::constructible_from constexpr explicit unique_handle(std::in_place_t, const delete_action_type& deleteAction, TArgs&&... args) @@ -250,6 +297,11 @@ namespace sl { } + /** + * \brief Constructor overload for directly initializing the value with a set of arguments. + * \tparam TArgs Type of the provided arguments. + * \param args Used arguments to initialize the value + */ template requires std::constructible_from SL_UNIQUE_HANDLE_FULL_CONSTEXPR T& emplace(TArgs&&... args) From 0424cfc2c5de9b191f5fa21ba0ad8b1245cd0cd5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 02:50:57 +0100 Subject: [PATCH 44/59] fix: unique_handle::emplace shouldn't return anything Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index ea5380af3..25c20011c 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -304,9 +304,9 @@ namespace sl */ template requires std::constructible_from - SL_UNIQUE_HANDLE_FULL_CONSTEXPR T& emplace(TArgs&&... args) + SL_UNIQUE_HANDLE_FULL_CONSTEXPR void emplace(TArgs&&... args) { - return m_Value.emplace(std::forward(args)...); + m_Value.emplace(std::forward(args)...); } SL_UNIQUE_HANDLE_FULL_CONSTEXPR void reset() noexcept From c7eb6d8358f2d3e10f459cca4eb5d38370f73c6f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 02:57:05 +0100 Subject: [PATCH 45/59] docs: adjust unique_handle docs Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 25c20011c..e21aba437 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -309,6 +309,9 @@ namespace sl m_Value.emplace(std::forward(args)...); } + /** + * \brief Resets the value and invokes the delete action if value was initialized. + */ SL_UNIQUE_HANDLE_FULL_CONSTEXPR void reset() noexcept { invoke_delete_action_if_necessary(); @@ -316,15 +319,29 @@ namespace sl m_Value.reset(); } + /** + * \brief Immutable access to the value. No checks will be performed. + * \exception Throws bad_handle_access if value is uninitialized. + * \return A const reference to the value + */ [[nodiscard]] constexpr const T& raw() const { return m_Value.value(); } + /** + * \brief Immutable access to the value. No checks will be performed. + * \return A const reference to the value + */ [[nodiscard]] constexpr const T& operator *() const noexcept { return *m_Value; } + [[nodiscard]] constexpr T* operator ->() noexcept { return &*m_Value; } + /** + * \brief Immutable access to the value. No checks will be performed. + * \return A const pointer to the value + */ [[nodiscard]] constexpr const T* operator ->() const noexcept { return &*m_Value; } From 721a7f492e1e1266819f30ea3412cb6468bab0db Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 02:57:30 +0100 Subject: [PATCH 46/59] fix: remove non-const operator -> of unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index e21aba437..dd419bc4a 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -334,10 +334,6 @@ namespace sl [[nodiscard]] constexpr const T& operator *() const noexcept { return *m_Value; } - - [[nodiscard]] - constexpr T* operator ->() noexcept { return &*m_Value; } - /** * \brief Immutable access to the value. No checks will be performed. * \return A const pointer to the value From 800beea5bf353373f8b8caf84a7626f095c25dd2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 03:07:54 +0100 Subject: [PATCH 47/59] docs: finalize unique_handle docs Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 39 +++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index dd419bc4a..4c4ddc53e 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -14,7 +14,7 @@ #include "Simple-Utility/concepts/stl_counterparts.hpp" -// some of the std::optional interface hasn't declared constexpr before +// some of the std::optional interface hasn't been declared constexpr before #if __cpp_lib_optional >= 202106 #define SL_UNIQUE_HANDLE_FULL_CONSTEXPR constexpr #else @@ -341,12 +341,24 @@ namespace sl [[nodiscard]] constexpr const T* operator ->() const noexcept { return &*m_Value; } + /** + * \brief Checks whether the value is initialized. + * \return True if value is initialized + */ [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_Value.has_value(); } + /** + * \brief Checks whether the value is initialized. + * \return True if value is initialized + */ [[nodiscard]] constexpr bool is_valid() const noexcept { return m_Value.has_value(); } + /** + * \brief Immutable access to the delete action. + * \return A const reference to the delete action + */ [[nodiscard]] constexpr const delete_action_type& delete_action() const noexcept { return m_DeleteAction; } @@ -363,6 +375,15 @@ namespace sl } }; + /** + * \brief Three-way-comparison operator overload for two ``unique_handles``. + * \tparam T Value type of the handles + * \tparam TDeleteAction Delete action type of the handles + * \param lhs left-hand-side of the operation + * \param rhs right-hand-side of the operation + * \return If both handles have initialized values, both values will be compared. Otherwise they will be compared in accordance + * to the result of ``is_valid()``. + */ template [[nodiscard]] constexpr std::compare_three_way_result_t operator <=> @@ -378,6 +399,15 @@ namespace sl return lhs.is_valid() <=> rhs.is_valid(); } + /** + * \brief Three-way-comparison operator overload for comparison of ``unique_handle`` and a value. + * \tparam T Value type of the handles + * \tparam TDeleteAction Delete action type of the handles + * \tparam T2 Type of right-hand-side. Must be three-way-comparable to ``T`` + * \param lhs left-hand-side of the operation + * \param rhs right-hand-side of the operation + * \return If the handle's value is initialized, both values will be compared. Otherwise handle is less. + */ template T2> [[nodiscard]] constexpr std::compare_three_way_result_t operator <=>(const unique_handle& lhs, const T2& rhs) @@ -389,6 +419,13 @@ namespace sl return std::compare_three_way_result_t::less; } + /** + * \brief Three-way-comparison operator overload for comparison of ``unique_handle`` and ``nullhandle_t`` + * \tparam T Value type of the handles + * \tparam TDeleteAction Delete action type of the handles + * \param lhs left-hand-side of the operation + * \return Returns ``std::strong_ordering::equal`` if handle's value is uninitialized, otherwise ``std::strong_ordering::greater``. + */ template [[nodiscard]] constexpr std::strong_ordering operator <=>(const unique_handle& lhs, nullhandle_t) noexcept From cc12da1f81c0f84ce7d0f84730db435f20fc697b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 03:09:02 +0100 Subject: [PATCH 48/59] docs: fix wording Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 4c4ddc53e..690f07237 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -110,7 +110,7 @@ namespace sl * // no cleanup necessary * } * ``` - * Of course, at a first glance this is quite more verbose, but in the long term nobody has to care about that entity anymore. This is what ``RAII`` is about. + * Of course, at a first glance this seems quite more verbose, but in the long term nobody has to care about that entity anymore. This is what ``RAII`` is about. * Note that ``unique_handles`` also can be stored as a member, then they really begin to shine, because if one would like to bind that entity to the lifetime of * an other object that would of course lead to custom move constructor, assignment operator and destructor and explicitly deleted copy. With a * ``unique_handle`` none of this is necessary (and this is in fact the main reason why I decided to implement this). From c8f8f20f40c76378ef70858b27f66eb55d9ffedf Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 03:32:02 +0100 Subject: [PATCH 49/59] docs: finalize unique_handle docs Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 104 ++++++++++++----------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 690f07237..2dcb226c9 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -24,60 +24,28 @@ namespace sl { /** - * \defgroup unique_handle unique_handle + * \defgroup unique_handle_module unique_handle * @{ - */ - - /** - * \brief helper type for indicating unique_handles with uninitialized state - */ - // ReSharper disable once IdentifierTypo - struct nullhandle_t - { - }; - - /** - * \brief an object of type nullhandle_t - */ - // ReSharper disable once IdentifierTypo - constexpr nullhandle_t nullhandle{}; - - /** - * \brief exception type which indicates checked access to an uninitialized value - */ - using bad_handle_access = std::bad_optional_access; - - /** - * \brief default delete action for unique_handle with an empty operator () - * \tparam T type to operate on - */ - template - struct default_delete_action - { - constexpr void operator ()(T&) const noexcept - { - } - }; - - /** - * \brief This type models some kind of ``std::optional`` behaviour but resets itself on move - * \details This type is in fact a wrapper around a ``std::optional``, thus has at least the overhead of that. Additionally it adds two + * + * \details The class \ref unique_handle is in fact a wrapper around a ``std::optional``, thus has at least the overhead of that. Additionally it adds two * important aspects: * -# it resets its internal value after it got moved. * -# it invokes its delete action every time when the internal value switches its state from initialized to uninitialized. * - * The latter happens when the value is in an initialized state and the ``unique_handle`` gets + * The latter happens when the value is in an initialized state and the \ref unique_handle gets * - moved, * - destructed or * - assigned * + * This is very similar to the behaviour of a ``std::unique_ptr``, hence the name. * This behaviour is useful in cases when one has an identifier to a resource, which isn't stored on the heap (thus a ``std::unique_ptr`` is not * a good option), and this identifier should have the responsibility as an owner over that resource, but the resource itself is not bound to the * lifetime of that identifier. This might sound quite abstract, thus let us visit a simple example. * * Here some entities are stored in a simple ``std::list``. Imagine this entities are accessible from many places in your program, thus something * like this can easily happen. - * ```cpp + * + * \code{.cpp} * std::list entities{}; * * { @@ -89,10 +57,12 @@ namespace sl * // entity should now be erased. Not actually c++-ig, is it? * entities.erase(entity_id); * } - * ``` + * \endcode + * * This is clearly no ``memory leak`` but if one forgets to erase the entity, it exists until the list is cleared. - * With ``unique_handle`` one can do this. - * ```cpp + * With \ref unique_handle one can do this. + * + * \code{.cpp} * struct list_delete_action * { * std::list* list{}; // pointer here, because a delete action must be move and copyable @@ -109,12 +79,50 @@ namespace sl * * // no cleanup necessary * } - * ``` + * \endcode + * * Of course, at a first glance this seems quite more verbose, but in the long term nobody has to care about that entity anymore. This is what ``RAII`` is about. * Note that ``unique_handles`` also can be stored as a member, then they really begin to shine, because if one would like to bind that entity to the lifetime of * an other object that would of course lead to custom move constructor, assignment operator and destructor and explicitly deleted copy. With a - * ``unique_handle`` none of this is necessary (and this is in fact the main reason why I decided to implement this). + * \ref unique_handle none of this is necessary (and this is in fact the main reason why I decided to implement this). * + * \see https://en.cppreference.com/w/cpp/utility/optional https://en.cppreference.com/w/cpp/memory/unique_ptr + */ + + /** + * \brief helper type for indicating unique_handles with uninitialized state + */ + // ReSharper disable once IdentifierTypo + struct nullhandle_t + { + }; + + /** + * \brief an object of type nullhandle_t + */ + // ReSharper disable once IdentifierTypo + constexpr nullhandle_t nullhandle{}; + + /** + * \brief exception type which indicates checked access to an uninitialized value + */ + using bad_handle_access = std::bad_optional_access; + + /** + * \brief default delete action for unique_handle with an empty operator () + * \tparam T type to operate on + */ + template + struct default_delete_action + { + constexpr void operator ()(T&) const noexcept + { + } + }; + + /** + * \brief This type models some kind of ``std::optional`` behaviour but resets itself on move operations. + * \details For more details and information about related components go to \ref unique_handle_module "unique_handle module page". * \tparam T The type of the stored value * \tparam TDeleteAction Type of the used delete action */ @@ -206,7 +214,7 @@ namespace sl /** * \brief Explicitly does not initialize the value. This overload is mainly used for convenience when returning - * a nullhandle. + * a \ref nullhandle. * \param deleteAction The provided delete action object */ constexpr unique_handle(nullhandle_t, const delete_action_type& deleteAction = delete_action_type()) noexcept @@ -376,7 +384,7 @@ namespace sl }; /** - * \brief Three-way-comparison operator overload for two ``unique_handles``. + * \brief Three-way-comparison operator overload between two \ref unique_handle "unique_handles". * \tparam T Value type of the handles * \tparam TDeleteAction Delete action type of the handles * \param lhs left-hand-side of the operation @@ -400,7 +408,7 @@ namespace sl } /** - * \brief Three-way-comparison operator overload for comparison of ``unique_handle`` and a value. + * \brief Three-way-comparison operator overload for comparison between a \ref unique_handle and a value. * \tparam T Value type of the handles * \tparam TDeleteAction Delete action type of the handles * \tparam T2 Type of right-hand-side. Must be three-way-comparable to ``T`` @@ -420,7 +428,7 @@ namespace sl } /** - * \brief Three-way-comparison operator overload for comparison of ``unique_handle`` and ``nullhandle_t`` + * \brief Three-way-comparison operator overload for comparison of \ref unique_handle and \ref nullhandle_t * \tparam T Value type of the handles * \tparam TDeleteAction Delete action type of the handles * \param lhs left-hand-side of the operation From 0424792a24e2e6a40898b9e5f3386288a6b014e5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 03:42:42 +0100 Subject: [PATCH 50/59] add: explicit deduction guide for unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 8 ++++++++ tests/unique_handle.cpp | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 2dcb226c9..82b196dc9 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -383,6 +383,14 @@ namespace sl } }; + /** + * \brief Deduction guide for \ref unique_handle class + * \tparam T Value type + * \tparam TDeleteAction Delete action type + */ + template + unique_handle(T, TDeleteAction) -> unique_handle; + /** * \brief Three-way-comparison operator overload between two \ref unique_handle "unique_handles". * \tparam T Value type of the handles diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 66df019f4..e0f9faeaa 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -151,6 +151,13 @@ TEST_CASE("unique_handle should be assignable by value.", "[unique_handle]") REQUIRE(handle); } +TEST_CASE("unique_handle should automatically deduct its template arguments when constructed by value and deleteAction.", "[unique_handle]") +{ + constexpr unique_handle handle{ 42, delete_action_mock{} }; + + // nothing to check, just compiling +} + TEST_CASE("unique_handle should be explicitly in-place construct value when std::in_place token is used.", "[unique_handle]") { constexpr unique_handle handle{ std::in_place, 42, 1337 }; From 45042f0690c2cbd20f3539aeca9bd7f3020f78a1 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 13:15:04 +0100 Subject: [PATCH 51/59] fix: make CATD of unique_handle with capturing lambda working Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 82b196dc9..948ef2dbf 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -126,8 +126,9 @@ namespace sl * \tparam T The type of the stored value * \tparam TDeleteAction Type of the used delete action */ - template TDeleteAction = default_delete_action> + template > requires std::copyable + && std::invocable // constraint has to be placed here, because in template argument list CTAD fails class unique_handle { public: From 3dd1087e9f36be7e871190d92e7126bc0227c413 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 13:15:46 +0100 Subject: [PATCH 52/59] add: unique_handle deduction guide for one parameter Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 948ef2dbf..5f61a3cf8 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -392,6 +392,13 @@ namespace sl template unique_handle(T, TDeleteAction) -> unique_handle; + /** + * \brief Deduction guide for \ref unique_handle class + * \tparam T Value type + */ + template + unique_handle(T) -> unique_handle; + /** * \brief Three-way-comparison operator overload between two \ref unique_handle "unique_handles". * \tparam T Value type of the handles From cbcea14c70ca83463ede03438d5594de82a81db3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 13:17:26 +0100 Subject: [PATCH 53/59] fix: add constraint for T not to be nullhandle_t in unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 5f61a3cf8..37e1e8d3a 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -129,6 +129,7 @@ namespace sl template > requires std::copyable && std::invocable // constraint has to be placed here, because in template argument list CTAD fails + && concepts::not_same_as class unique_handle { public: From 925d3860b4947dc46688bd1203e23005fb39197b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 13:18:28 +0100 Subject: [PATCH 54/59] fix: remove template param from default_delete_action Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 37e1e8d3a..05eb323e0 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -110,12 +110,10 @@ namespace sl /** * \brief default delete action for unique_handle with an empty operator () - * \tparam T type to operate on */ - template struct default_delete_action { - constexpr void operator ()(T&) const noexcept + constexpr void operator ()(auto&) const noexcept { } }; @@ -126,7 +124,7 @@ namespace sl * \tparam T The type of the stored value * \tparam TDeleteAction Type of the used delete action */ - template > + template requires std::copyable && std::invocable // constraint has to be placed here, because in template argument list CTAD fails && concepts::not_same_as From 0ac99324dc57b4b1b291765201c588d3387b8fba Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 13:20:34 +0100 Subject: [PATCH 55/59] refactor: rename element_type to value_type in unique_handle Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 05eb323e0..6ea907997 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -131,7 +131,13 @@ namespace sl class unique_handle { public: - using element_type = T; + /** + * \brief Type of the stored value + */ + using value_type = T; + /** + * \brief Type of the used delete action + */ using delete_action_type = TDeleteAction; /** From 6d6922770a1df6ac9a97d1dee0752051e402a40d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 14:16:02 +0100 Subject: [PATCH 56/59] improve unique_handle deduction guides Signed-off-by: DNKpp --- include/Simple-Utility/unique_handle.hpp | 51 +++++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/include/Simple-Utility/unique_handle.hpp b/include/Simple-Utility/unique_handle.hpp index 6ea907997..8059f5719 100644 --- a/include/Simple-Utility/unique_handle.hpp +++ b/include/Simple-Utility/unique_handle.hpp @@ -86,7 +86,12 @@ namespace sl * an other object that would of course lead to custom move constructor, assignment operator and destructor and explicitly deleted copy. With a * \ref unique_handle none of this is necessary (and this is in fact the main reason why I decided to implement this). * - * \see https://en.cppreference.com/w/cpp/utility/optional https://en.cppreference.com/w/cpp/memory/unique_ptr + * \note As using lambdas as delete action is usually fine, using capturing lambdas will fail to compile because they are non-copy-assignable. Use a old-school + * invokable struct as in the example instead. + * + * \see https://en.cppreference.com/w/cpp/utility/optional + * \see https://en.cppreference.com/w/cpp/memory/unique_ptr + * \see https://en.cppreference.com/w/cpp/language/lambda */ /** @@ -118,16 +123,46 @@ namespace sl } }; + /** @} */ + + namespace detail + { + template + struct value_validator + { + static_assert(std::movable, "The value type must be movable."); + static_assert(concepts::not_same_as, nullhandle_t>, "The value type must not be sl::nullhandle_t."); + + using type = T; + }; + + template + struct delete_action_validator + { + static_assert(std::copyable, "The delete action object must be copyable (capturing lambdas are not)."); + static_assert(std::invocable, "The delete action object must be invokable by T&."); + + using type = TDeleteAction; + }; + + template + using type_t = typename T::type; + } + + /** + * \addtogroup unique_handle_module + * @{ + */ + /** * \brief This type models some kind of ``std::optional`` behaviour but resets itself on move operations. * \details For more details and information about related components go to \ref unique_handle_module "unique_handle module page". * \tparam T The type of the stored value * \tparam TDeleteAction Type of the used delete action */ - template - requires std::copyable - && std::invocable // constraint has to be placed here, because in template argument list CTAD fails - && concepts::not_same_as + template TDeleteAction = default_delete_action> + requires concepts::not_same_as + && std::copyable class unique_handle { public: @@ -395,7 +430,11 @@ namespace sl * \tparam TDeleteAction Delete action type */ template - unique_handle(T, TDeleteAction) -> unique_handle; + unique_handle + ( + T, + TDeleteAction + ) -> unique_handle>, detail::type_t>>; /** * \brief Deduction guide for \ref unique_handle class From 3f535370cf780333ac033ac33fa8cf7592362008 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 14:16:18 +0100 Subject: [PATCH 57/59] docs: exclude sl::detail namespace explicitly Signed-off-by: DNKpp --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index f4eb4cebb..fd032823e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -989,7 +989,7 @@ EXCLUDE_PATTERNS = # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = sl::detail # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include From 174ffe588d01bbe58129254bf44ccf2e3b5ce340 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 14:16:48 +0100 Subject: [PATCH 58/59] test: extend deduction guide tests of unique_handle Signed-off-by: DNKpp --- tests/unique_handle.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index e0f9faeaa..1e508d9dc 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -155,7 +155,16 @@ TEST_CASE("unique_handle should automatically deduct its template arguments when { constexpr unique_handle handle{ 42, delete_action_mock{} }; - // nothing to check, just compiling + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); +} + +TEST_CASE("unique_handle should automatically deduct its template arguments when constructed by value.", "[unique_handle]") +{ + constexpr unique_handle handle{ 42 }; + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); } TEST_CASE("unique_handle should be explicitly in-place construct value when std::in_place token is used.", "[unique_handle]") From 5f276186435fa8e35b64aefd224cdc00c51021c5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 30 Dec 2021 18:59:03 +0100 Subject: [PATCH 59/59] test: please clang <= 10 Signed-off-by: DNKpp --- tests/unique_handle.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/unique_handle.cpp b/tests/unique_handle.cpp index 1e508d9dc..da4c28a0c 100644 --- a/tests/unique_handle.cpp +++ b/tests/unique_handle.cpp @@ -212,11 +212,11 @@ TEMPLATE_TEST_CASE_SIG ( "delete action on assignment must only be invoked if unique_handle holds a value.", "[unique_handle]", - ((auto VInit, auto VAssign, bool VExpected), VInit, VAssign, VExpected), - (42, 1337, true), - (42, nullhandle, true), - (nullhandle, 1337, false), - (nullhandle, nullhandle, false) + ((class TInit, class TAssign, bool VExpected), TInit, TAssign, VExpected), + (int, int, true), + (int, nullhandle_t, true), + (nullhandle_t, int, false), + (nullhandle_t, nullhandle_t, false) ) #pragma warning(disable: 26444) { @@ -224,8 +224,8 @@ TEMPLATE_TEST_CASE_SIG const bool result = [] { int counter{}; - test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; - temp = VAssign; + test_handle temp{ TInit{}, delete_action_mock{ .invoke_counter = &counter } }; + temp = TAssign{}; return counter == 1; }(); @@ -408,9 +408,9 @@ TEMPLATE_TEST_CASE_SIG ( "delete action on reset must only be invoked if unique_handle holds a value.", "[unique_handle]", - ((auto VInit, bool VExpected), VInit, VExpected), - (42, true), - (nullhandle, false) + ((class TInit, bool VExpected), TInit, VExpected), + (int, true), + (nullhandle_t, false) ) #pragma warning(disable: 26444) { @@ -418,7 +418,7 @@ TEMPLATE_TEST_CASE_SIG const bool result = [] { int counter{}; - test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; + test_handle temp{ TInit{}, delete_action_mock{ .invoke_counter = &counter } }; temp.reset(); return counter == 1; }(); @@ -443,9 +443,9 @@ TEMPLATE_TEST_CASE_SIG ( "delete action on destruction must only be invoked if unique_handle holds a value.", "[unique_handle]", - ((auto VInit, bool VExpected), VInit, VExpected), - (42, true), - (nullhandle, false) + ((class TInit, bool VExpected), TInit, VExpected), + (int, true), + (nullhandle_t, false) ) #pragma warning(disable: 26444) { @@ -453,7 +453,7 @@ TEMPLATE_TEST_CASE_SIG { int counter{}; { - const test_handle temp{ VInit, delete_action_mock{ .invoke_counter = &counter } }; + const test_handle temp{ TInit{}, delete_action_mock{ .invoke_counter = &counter } }; } return counter == 1; }();