From defa7e1b9e16c6276e17a39b61d019c1a116472b Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Fri, 22 Nov 2024 18:15:40 +0100 Subject: Use niche in option --- asl/meta.hpp | 3 + asl/option.hpp | 176 +++++++++++++++++++++++++++++++-------------- asl/tests/option_tests.cpp | 33 +++++++++ 3 files changed, 160 insertions(+), 52 deletions(-) (limited to 'asl') diff --git a/asl/meta.hpp b/asl/meta.hpp index 72b36fd..ab25a88 100644 --- a/asl/meta.hpp +++ b/asl/meta.hpp @@ -169,4 +169,7 @@ concept has_niche = constructible_from && { value == n } -> same_as; }; +template +concept is_niche = same_as, niche>; + } // namespace asl diff --git a/asl/option.hpp b/asl/option.hpp index 5f53ad8..6b528b9 100644 --- a/asl/option.hpp +++ b/asl/option.hpp @@ -4,6 +4,7 @@ #include "asl/meta.hpp" #include "asl/maybe_uninit.hpp" #include "asl/functional.hpp" +#include "asl/annotations.hpp" namespace asl { @@ -11,7 +12,6 @@ namespace asl struct nullopt_t {}; static constexpr nullopt_t nullopt{}; -// @Todo(option) Niche // @Todo(option) Reference // @Todo(option) Function // @Todo(option) Arrays @@ -71,31 +71,42 @@ class option trivially_move_assignable && trivially_destructible; - using Storage = select_t>; + static constexpr bool kHasNiche = has_niche; + + static constexpr bool kHasInlinePayload = kIsTrivial || kHasNiche; + + using Storage = select_t>; + using HasValueMarker = select_t; Storage m_payload{}; - bool m_has_value = false; + ASL_NO_UNIQUE_ADDRESS HasValueMarker m_has_value; template - constexpr void construct(Args&&... args) & + constexpr void construct(Args&&... args) { - ASL_ASSERT(!m_has_value); - m_has_value = true; - if constexpr (kIsTrivial) + ASL_ASSERT(!has_value()); + + if constexpr (kHasInlinePayload) { - new(&m_payload) T(ASL_FWD(args)...); + new (&m_payload) T(ASL_FWD(args)...); } else { m_payload.init_unsafe(ASL_FWD(args)...); } + + if constexpr (!kHasNiche) + { + m_has_value = true; + } } - template - constexpr void assign(Arg&& arg) & + template + constexpr void assign(U&& arg) { - ASL_ASSERT(m_has_value); - if constexpr (kIsTrivial) + ASL_ASSERT(has_value()); + + if constexpr (kHasInlinePayload) { m_payload = ASL_FWD(arg); } @@ -108,29 +119,69 @@ class option public: using type = T; - constexpr option() = default; - constexpr option(nullopt_t) {} // NOLINT(*-explicit-conversions) + constexpr option() : option(nullopt) {} + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr option(nullopt_t) requires (!kHasNiche) + : m_has_value{false} + , m_payload{} + {} + + // NOLINTNEXTLINE(*-explicit-conversions) + constexpr option(nullopt_t) requires kHasNiche + : m_payload{niche{}} + {} template constexpr explicit (!convertible_from) option(U&& value) requires ( + kHasNiche && constructible_from && - !same_as, option> + !same_as, option> && + !is_niche + ) + : m_payload(ASL_FWD(value)) + {} + + template + constexpr explicit (!convertible_from) + option(U&& value) + requires ( + kHasInlinePayload && + !kHasNiche && + constructible_from && + !same_as, option> && + !is_niche + ) + : m_payload(ASL_FWD(value)) + , m_has_value{true} + {} + + template + constexpr explicit (!convertible_from) + option(U&& value) + requires ( + !kHasInlinePayload && + constructible_from && + !same_as, option> && + !is_niche ) + : option() { construct(ASL_FWD(value)); } constexpr option(const option& other) - requires copy_constructible && kIsTrivial = default; + requires copy_constructible && kHasInlinePayload = default; constexpr option(const option& other) - requires copy_constructible && (!kIsTrivial) + requires copy_constructible && (!kHasInlinePayload) + : option() { - if (other.m_has_value) + if (other.has_value()) { - construct(other.m_payload.as_init_unsafe()); + construct(other.value()); } } @@ -138,17 +189,21 @@ public: requires (!copy_constructible) = delete; constexpr option(option&& other) - requires move_constructible && kIsTrivial = default; + requires move_constructible && kHasInlinePayload = default; constexpr option(option&& other) - requires move_constructible && (!kIsTrivial) + requires move_constructible && (!kHasInlinePayload) + : option() { - if (other.m_has_value) + if (other.has_value()) { - construct(ASL_MOVE(other.m_payload.as_init_unsafe())); + construct(ASL_MOVE(other.value())); } } + constexpr option(option&& other) + requires (!move_constructible) = delete; + template constexpr explicit (!convertible_from) option(const option& other) @@ -156,6 +211,7 @@ public: constructible_from && !option_internal::convertible_constructible_from_option ) + : option() { if (other.has_value()) { @@ -170,10 +226,11 @@ public: constructible_from && !option_internal::convertible_constructible_from_option ) + : option() { if (other.has_value()) { - construct(ASL_MOVE(other).value()); + construct(ASL_MOVE(other.value())); } } @@ -191,7 +248,7 @@ public: !same_as, option> ) { - if (m_has_value) + if (has_value()) { assign(ASL_FWD(value)); } @@ -204,19 +261,19 @@ public: } constexpr option& operator=(const option& other) & - requires (!copy_assignable || copy_constructible) = delete; + requires (!copy_assignable || !copy_constructible) = delete; constexpr option& operator=(const option& other) & - requires copy_assignable && copy_constructible && kIsTrivial = default; + requires copy_assignable && copy_constructible && kHasInlinePayload = default; constexpr option& operator=(const option& other) & - requires copy_assignable && copy_constructible && (!kIsTrivial) + requires copy_assignable && copy_constructible && (!kHasInlinePayload) { if (&other == this) { return *this; } - if (other.m_has_value) + if (other.has_value()) { - if (m_has_value) + if (has_value()) { assign(other.m_payload.as_init_unsafe()); } @@ -225,7 +282,7 @@ public: construct(other.m_payload.as_init_unsafe()); } } - else + else if (has_value()) { reset(); } @@ -234,16 +291,16 @@ public: } constexpr option& operator=(option&& other) & - requires move_assignable && move_constructible && kIsTrivial = default; + requires move_assignable && move_constructible && kHasInlinePayload = default; constexpr option& operator=(option&& other) & - requires move_assignable && move_constructible && (!kIsTrivial) + requires move_assignable && move_constructible && (!kHasInlinePayload) { if (&other == this) { return *this; } - if (other.m_has_value) + if (other.has_value()) { - if (m_has_value) + if (has_value()) { assign(ASL_MOVE(other.m_payload.as_init_unsafe())); } @@ -252,7 +309,7 @@ public: construct(ASL_MOVE(other.m_payload.as_init_unsafe())); } } - else + else if (has_value()) { reset(); } @@ -270,7 +327,7 @@ public: { if (other.has_value()) { - if (m_has_value) + if (has_value()) { assign(other.value()); } @@ -279,7 +336,7 @@ public: construct(other.value()); } } - else + else if (has_value()) { reset(); } @@ -297,7 +354,7 @@ public: { if (other.has_value()) { - if (m_has_value) + if (has_value()) { assign(ASL_MOVE(other).value()); } @@ -306,7 +363,7 @@ public: construct(ASL_MOVE(other).value()); } } - else + else if (has_value()) { reset(); } @@ -322,23 +379,38 @@ public: constexpr void reset() { - if constexpr (kIsTrivial) + if (!has_value()) { return; } + + if constexpr (kHasNiche) { - m_has_value = false; + m_payload = T(niche{}); } - else if (m_has_value) + else { - m_payload.uninit_unsafe(); m_has_value = false; + if constexpr (!kHasInlinePayload) + { + m_payload.uninit_unsafe(); + } } } - constexpr bool has_value() const { return m_has_value; } + constexpr bool has_value() const + { + if constexpr (kHasNiche) + { + return m_payload != niche{}; + } + else + { + return m_has_value; + } + } constexpr T&& value() && { - ASL_ASSERT_RELEASE(m_has_value); - if constexpr (kIsTrivial) + ASL_ASSERT_RELEASE(has_value()); + if constexpr (kHasInlinePayload) { return ASL_MOVE(m_payload); } @@ -350,8 +422,8 @@ public: constexpr T& value() & { - ASL_ASSERT_RELEASE(m_has_value); - if constexpr (kIsTrivial) + ASL_ASSERT_RELEASE(has_value()); + if constexpr (kHasInlinePayload) { return m_payload; } @@ -363,8 +435,8 @@ public: constexpr const T& value() const& { - ASL_ASSERT_RELEASE(m_has_value); - if constexpr (kIsTrivial) + ASL_ASSERT_RELEASE(has_value()); + if constexpr (kHasInlinePayload) { return m_payload; } @@ -392,7 +464,7 @@ public: constexpr T& emplace(Args&&... args) & requires constructible_from { - if (m_has_value) { reset(); } + if (has_value()) { reset(); } construct(ASL_FWD(args)...); return value(); } diff --git a/asl/tests/option_tests.cpp b/asl/tests/option_tests.cpp index 4c65892..8b98c99 100644 --- a/asl/tests/option_tests.cpp +++ b/asl/tests/option_tests.cpp @@ -5,6 +5,25 @@ class Base {}; class Derived : public Base {}; +struct NonZero +{ + int value; + + constexpr explicit NonZero(int x) : value(x) + { + ASL_ASSERT(x != 0); + } + + constexpr explicit NonZero(asl::niche) : value(0) {} + + constexpr bool operator==(asl::niche) const { return value == 0; } +}; +static_assert(asl::has_niche); +static_assert(!asl::has_niche); + +static_assert(sizeof(asl::option) > sizeof(int)); +static_assert(sizeof(asl::option) == sizeof(NonZero)); + static_assert(!asl::is_option); static_assert(asl::is_option>); static_assert(asl::is_option>); @@ -276,3 +295,17 @@ ASL_TEST(or_else) ASL_TEST_ASSERT(b2.has_value()); ASL_TEST_EXPECT(b2.value() == 12); } + +ASL_TEST(niche) +{ + asl::option opt; + ASL_TEST_EXPECT(!opt.has_value()); + + asl::option opt2(2); + ASL_TEST_EXPECT(opt2.has_value()); + ASL_TEST_EXPECT(opt2.value().value == 2); + + opt = opt2; + ASL_TEST_EXPECT(opt2.has_value()); + ASL_TEST_EXPECT(opt2.value().value == 2); +} -- cgit