diff --git a/modules/juce_core/containers/juce_Optional.h b/modules/juce_core/containers/juce_Optional.h index 6e2333ce98..7fcd584b64 100644 --- a/modules/juce_core/containers/juce_Optional.h +++ b/modules/juce_core/containers/juce_Optional.h @@ -23,38 +23,19 @@ namespace juce { -namespace detail -{ -namespace adlSwap -{ -using std::swap; - -template -constexpr auto isNothrowSwappable = noexcept (swap (std::declval(), std::declval())); -} // namespace adlSwap -} // namespace detail - -/** A type representing the null state of an Optional. - Similar to std::nullopt_t. -*/ -struct Nullopt -{ - explicit constexpr Nullopt (int) {} -}; - -/** An object that can be used when constructing and comparing Optional instances. - Similar to std::nullopt. -*/ -constexpr Nullopt nullopt { 0 }; +using Nullopt = std::nullopt_t; +constexpr auto nullopt = std::nullopt; // Without this, our tests can emit "unreachable code" warnings during // link time code generation. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702) +#define JUCE_OPTIONAL_OPERATORS X(==) X(!=) X(<) X(<=) X(>) X(>=) + /** A simple optional type. - Has similar (not necessarily identical!) semantics to std::optional. + In new code, you should probably prefer using std::optional directly. This is intended to stand-in for std::optional while JUCE's minimum supported language standard is lower than C++17. When the minimum language @@ -72,224 +53,104 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702) template class Optional { - template - struct NotConstructibleFromSimilarType - { - static constexpr auto value = ! std::is_constructible&>::value - && ! std::is_constructible&>::value - && ! std::is_constructible&&>::value - && ! std::is_constructible&&>::value - && ! std::is_convertible&, T>::value - && ! std::is_convertible&, T>::value - && ! std::is_convertible&&, T>::value - && ! std::is_convertible&&, T>::value; - }; - - template - using OptionalCopyConstructorEnabled = std::enable_if_t::value && NotConstructibleFromSimilarType::value>; - - template - using OptionalMoveConstructorEnabled = std::enable_if_t::value && NotConstructibleFromSimilarType::value>; - - template - static auto notAssignableFromSimilarType = NotConstructibleFromSimilarType::value - && ! std::is_assignable&>::value - && ! std::is_assignable&>::value - && ! std::is_assignable&&>::value - && ! std::is_assignable&&>::value; - - template - using OptionalCopyAssignmentEnabled = std::enable_if_t::value - && std::is_assignable::value - && NotConstructibleFromSimilarType::value>; - - template - using OptionalMoveAssignmentEnabled = std::enable_if_t::value - && std::is_nothrow_assignable::value - && NotConstructibleFromSimilarType::value>; + template struct IsOptional : std::false_type {}; + template struct IsOptional> : std::true_type {}; public: - Optional() : placeholder() {} + Optional() = default; + Optional (const Optional&) = default; + Optional (Optional&&) = default; + Optional& operator= (const Optional&) = default; + Optional& operator= (Optional&&) = default; - Optional (Nullopt) noexcept : placeholder() {} + Optional (Nullopt) noexcept {} - template ::value - && ! std::is_same, Optional>::value>> - Optional (U&& value) noexcept (noexcept (Value (std::forward (value)))) - : storage (std::forward (value)), valid (true) - { - } + template >::value, int> = 0> + Optional (Head&& head, Tail&&... tail) + noexcept (std::is_nothrow_constructible_v, Head, Tail...>) + : opt (std::forward (head), std::forward (tail)...) {} - Optional (Optional&& other) noexcept (noexcept (std::declval().constructFrom (other))) - : placeholder() - { - constructFrom (other); - } - - Optional (const Optional& other) - : placeholder(), valid (other.valid) - { - if (valid) - new (&storage) Value (*other); - } - - template > - Optional (Optional&& other) noexcept (noexcept (std::declval().constructFrom (other))) - : placeholder() - { - constructFrom (other); - } - - template > + template Optional (const Optional& other) - : placeholder(), valid (other.hasValue()) - { - if (valid) - new (&storage) Value (*other); - } + noexcept (std::is_nothrow_constructible_v, const std::optional&>) + : opt (other.opt) {} - Optional& operator= (Nullopt) noexcept + template + Optional (Optional&& other) + noexcept (std::is_nothrow_constructible_v, std::optional&&>) + : opt (std::move (other.opt)) {} + + template >::value, int> = 0> + Optional& operator= (Other&& other) + noexcept (std::is_nothrow_assignable_v, Other>) { - reset(); + opt = std::forward (other); return *this; } - template ::value - && std::is_nothrow_move_assignable::value>> - Optional& operator= (Optional&& other) noexcept (noexcept (std::declval().assign (std::declval()))) - { - assign (other); - return *this; - } - - template , Optional>::value - && std::is_constructible::value - && std::is_assignable::value - && (! std::is_scalar::value || ! std::is_same, Value>::value)>> - Optional& operator= (U&& value) - { - if (valid) - **this = std::forward (value); - else - new (&storage) Value (std::forward (value)); - - valid = true; - return *this; - } - - /** Maintains the strong exception safety guarantee. */ - Optional& operator= (const Optional& other) - { - auto copy = other; - assign (copy); - return *this; - } - - template > - Optional& operator= (Optional&& other) noexcept (noexcept (std::declval().assign (other))) - { - assign (other); - return *this; - } - - /** Maintains the strong exception safety guarantee. */ - template > + template Optional& operator= (const Optional& other) + noexcept (std::is_nothrow_assignable_v, const std::optional&>) { - auto copy = other; - assign (copy); + opt = other.opt; return *this; } - ~Optional() noexcept + template + Optional& operator= (Optional&& other) + noexcept (std::is_nothrow_assignable_v, std::optional&&>) { - reset(); + opt = std::move (other.opt); + return *this; } - Value* operator->() noexcept { return reinterpret_cast< Value*> (&storage); } - const Value* operator->() const noexcept { return reinterpret_cast (&storage); } - - Value& operator*() noexcept { return *operator->(); } - const Value& operator*() const noexcept { return *operator->(); } - - explicit operator bool() const noexcept { return valid; } - bool hasValue() const noexcept { return valid; } - - void reset() + template + auto& emplace (Other&&... other) { - if (std::exchange (valid, false)) - operator*().~Value(); + return opt.emplace (std::forward (other)...); } - /** Like std::optional::value_or */ + void reset() noexcept + { + opt.reset(); + } + + void swap (Optional& other) + noexcept (std::is_nothrow_swappable_v>) + { + opt.swap (other.opt); + } + + decltype (auto) operator->() { return opt.operator->(); } + decltype (auto) operator->() const { return opt.operator->(); } + decltype (auto) operator* () { return opt.operator* (); } + decltype (auto) operator* () const { return opt.operator* (); } + + operator bool() const noexcept { return opt.has_value(); } + bool hasValue() const noexcept { return opt.has_value(); } + template - Value orFallback (U&& fallback) const { return *this ? **this : std::forward (fallback); } + decltype (auto) orFallback (U&& fallback) const& { return opt.value_or (std::forward (fallback)); } - template - Value& emplace (Args&&... args) - { - reset(); - new (&storage) Value (std::forward (args)...); - valid = true; - return **this; - } + template + decltype (auto) orFallback (U&& fallback) & { return opt.value_or (std::forward (fallback)); } - void swap (Optional& other) noexcept (std::is_nothrow_move_constructible::value - && detail::adlSwap::isNothrowSwappable) - { - if (hasValue() && other.hasValue()) - { - using std::swap; - swap (**this, *other); - } - else if (hasValue() || other.hasValue()) - { - (hasValue() ? other : *this).constructFrom (hasValue() ? *this : other); - } - } + #define X(op) \ + template friend bool operator op (const Optional&, const Optional&); \ + template friend bool operator op (const Optional&, Nullopt); \ + template friend bool operator op (Nullopt, const Optional&); \ + template friend bool operator op (const Optional&, const U&); \ + template friend bool operator op (const T&, const Optional&); + + JUCE_OPTIONAL_OPERATORS + + #undef X private: template - void constructFrom (Optional& other) noexcept (noexcept (Value (std::move (*other)))) - { - if (! other.hasValue()) - return; + friend class Optional; - new (&storage) Value (std::move (*other)); - valid = true; - other.reset(); - } - - template - void assign (Optional& other) noexcept (noexcept (std::declval() = std::move (*other)) && noexcept (std::declval().constructFrom (other))) - { - if (valid) - { - if (other.hasValue()) - { - **this = std::move (*other); - other.reset(); - } - else - { - reset(); - } - } - else - { - constructFrom (other); - } - } - - union - { - char placeholder; - Value storage; - }; - bool valid = false; + std::optional opt; }; JUCE_END_IGNORE_WARNINGS_MSVC @@ -300,102 +161,17 @@ Optional> makeOptional (Value&& v) return std::forward (v); } -template -bool operator== (const Optional& lhs, const Optional& rhs) -{ - if (lhs.hasValue() != rhs.hasValue()) return false; - if (! lhs.hasValue()) return true; - return *lhs == *rhs; -} +#define X(op) \ + template bool operator op (const Optional& lhs, const Optional& rhs) { return lhs.opt op rhs.opt; } \ + template bool operator op (const Optional& lhs, Nullopt rhs) { return lhs.opt op rhs; } \ + template bool operator op (Nullopt lhs, const Optional& rhs) { return lhs op rhs.opt; } \ + template bool operator op (const Optional& lhs, const U& rhs) { return lhs.opt op rhs; } \ + template bool operator op (const T& lhs, const Optional& rhs) { return lhs op rhs.opt; } -template -bool operator!= (const Optional& lhs, const Optional& rhs) -{ - if (lhs.hasValue() != rhs.hasValue()) return true; - if (! lhs.hasValue()) return false; - return *lhs != *rhs; -} +JUCE_OPTIONAL_OPERATORS -template -bool operator< (const Optional& lhs, const Optional& rhs) -{ - if (! rhs.hasValue()) return false; - if (! lhs.hasValue()) return true; - return *lhs < *rhs; -} +#undef X -template -bool operator<= (const Optional& lhs, const Optional& rhs) -{ - if (! lhs.hasValue()) return true; - if (! rhs.hasValue()) return false; - return *lhs <= *rhs; -} - -template -bool operator> (const Optional& lhs, const Optional& rhs) -{ - if (! lhs.hasValue()) return false; - if (! rhs.hasValue()) return true; - return *lhs > *rhs; -} - -template -bool operator>= (const Optional& lhs, const Optional& rhs) -{ - if (! rhs.hasValue()) return true; - if (! lhs.hasValue()) return false; - return *lhs >= *rhs; -} - -template -bool operator== (const Optional& opt, Nullopt) noexcept { return ! opt.hasValue(); } -template -bool operator== (Nullopt, const Optional& opt) noexcept { return ! opt.hasValue(); } -template -bool operator!= (const Optional& opt, Nullopt) noexcept { return opt.hasValue(); } -template -bool operator!= (Nullopt, const Optional& opt) noexcept { return opt.hasValue(); } -template -bool operator< (const Optional&, Nullopt) noexcept { return false; } -template -bool operator< (Nullopt, const Optional& opt) noexcept { return opt.hasValue(); } -template -bool operator<= (const Optional& opt, Nullopt) noexcept { return ! opt.hasValue(); } -template -bool operator<= (Nullopt, const Optional&) noexcept { return true; } -template -bool operator> (const Optional& opt, Nullopt) noexcept { return opt.hasValue(); } -template -bool operator> (Nullopt, const Optional&) noexcept { return false; } -template -bool operator>= (const Optional&, Nullopt) noexcept { return true; } -template -bool operator>= (Nullopt, const Optional& opt) noexcept { return ! opt.hasValue(); } - -template -bool operator== (const Optional& opt, const U& value) { return opt.hasValue() ? *opt == value : false; } -template -bool operator== (const T& value, const Optional& opt) { return opt.hasValue() ? value == *opt : false; } -template -bool operator!= (const Optional& opt, const U& value) { return opt.hasValue() ? *opt != value : true; } -template -bool operator!= (const T& value, const Optional& opt) { return opt.hasValue() ? value != *opt : true; } -template -bool operator< (const Optional& opt, const U& value) { return opt.hasValue() ? *opt < value : true; } -template -bool operator< (const T& value, const Optional& opt) { return opt.hasValue() ? value < *opt : false; } -template -bool operator<= (const Optional& opt, const U& value) { return opt.hasValue() ? *opt <= value : true; } -template -bool operator<= (const T& value, const Optional& opt) { return opt.hasValue() ? value <= *opt : false; } -template -bool operator> (const Optional& opt, const U& value) { return opt.hasValue() ? *opt > value : false; } -template -bool operator> (const T& value, const Optional& opt) { return opt.hasValue() ? value > *opt : true; } -template -bool operator>= (const Optional& opt, const U& value) { return opt.hasValue() ? *opt >= value : false; } -template -bool operator>= (const T& value, const Optional& opt) { return opt.hasValue() ? value >= *opt : true; } +#undef JUCE_OPTIONAL_OPERATORS } // namespace juce diff --git a/modules/juce_core/containers/juce_Optional_test.cpp b/modules/juce_core/containers/juce_Optional_test.cpp index cc2b7994a8..b3e10de812 100644 --- a/modules/juce_core/containers/juce_Optional_test.cpp +++ b/modules/juce_core/containers/juce_Optional_test.cpp @@ -66,7 +66,8 @@ public: Optional original (ptr); expect (ptr.use_count() == 2); auto other = std::move (original); - expect (! original.hasValue()); + // A moved-from optional still contains a value! + expect (original.hasValue()); expect (other.hasValue()); expect (ptr.use_count() == 2); } @@ -104,7 +105,7 @@ public: aOpt = std::move (bOpt); expect (aOpt.hasValue()); - expect (! bOpt.hasValue()); + expect (bOpt.hasValue()); expect (a.use_count() == 1); expect (b.use_count() == 2); @@ -158,7 +159,7 @@ public: empty = std::move (aOpt); expect (empty.hasValue()); - expect (! aOpt.hasValue()); + expect (aOpt.hasValue()); expect (a.use_count() == 2); } @@ -265,7 +266,7 @@ public: expect (! a.hasValue()); } - beginTest ("Strong exception safety is maintained when copying over populated object"); + beginTest ("Exception safety of contained type is maintained when copying over populated object"); { bool threw = false; Optional a = ThrowOnCopy(); @@ -283,7 +284,7 @@ public: expect (threw); expect (a.hasValue()); - expect (a->value == 5); + expect (a->value == -100); } beginTest ("Assigning from nullopt clears the instance");