diff --git a/modules/juce_audio_basics/midi/juce_MidiFile.cpp b/modules/juce_audio_basics/midi/juce_MidiFile.cpp index 5d437aee76..67795b45c4 100644 --- a/modules/juce_audio_basics/midi/juce_MidiFile.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiFile.cpp @@ -46,18 +46,6 @@ namespace MidiFileHelpers } } - template - struct Optional - { - Optional() = default; - - Optional (const Value& v) - : value (v), valid (true) {} - - Value value = Value(); - bool valid = false; - }; - template struct ReadTrait; @@ -100,23 +88,23 @@ namespace MidiFileHelpers auto ch = tryRead (data, remaining); - if (! ch.valid) + if (! ch.hasValue()) return {}; - if (ch.value != ByteOrder::bigEndianInt ("MThd")) + if (*ch != ByteOrder::bigEndianInt ("MThd")) { auto ok = false; - if (ch.value == ByteOrder::bigEndianInt ("RIFF")) + if (*ch == ByteOrder::bigEndianInt ("RIFF")) { for (int i = 0; i < 8; ++i) { ch = tryRead (data, remaining); - if (! ch.valid) + if (! ch.hasValue()) return {}; - if (ch.value == ByteOrder::bigEndianInt ("MThd")) + if (*ch == ByteOrder::bigEndianInt ("MThd")) { ok = true; break; @@ -130,29 +118,29 @@ namespace MidiFileHelpers const auto bytesRemaining = tryRead (data, remaining); - if (! bytesRemaining.valid || bytesRemaining.value > remaining) + if (! bytesRemaining.hasValue() || *bytesRemaining > remaining) return {}; const auto optFileType = tryRead (data, remaining); - if (! optFileType.valid || 2 < optFileType.value) + if (! optFileType.hasValue() || 2 < *optFileType) return {}; const auto optNumTracks = tryRead (data, remaining); - if (! optNumTracks.valid || (optFileType.value == 0 && optNumTracks.value != 1)) + if (! optNumTracks.hasValue() || (*optFileType == 0 && *optNumTracks != 1)) return {}; const auto optTimeFormat = tryRead (data, remaining); - if (! optTimeFormat.valid) + if (! optTimeFormat.hasValue()) return {}; HeaderDetails result; - result.fileType = (short) optFileType.value; - result.timeFormat = (short) optTimeFormat.value; - result.numberOfTracks = (short) optNumTracks.value; + result.fileType = (short) *optFileType; + result.timeFormat = (short) *optTimeFormat; + result.numberOfTracks = (short) *optNumTracks; result.bytesRead = maxSize - remaining; return { result }; @@ -373,10 +361,10 @@ bool MidiFile::readFrom (InputStream& sourceStream, const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size); - if (! optHeader.valid) + if (! optHeader.hasValue()) return false; - const auto header = optHeader.value; + const auto header = *optHeader; timeFormat = header.timeFormat; d += header.bytesRead; @@ -386,20 +374,20 @@ bool MidiFile::readFrom (InputStream& sourceStream, { const auto optChunkType = MidiFileHelpers::tryRead (d, size); - if (! optChunkType.valid) + if (! optChunkType.hasValue()) return false; const auto optChunkSize = MidiFileHelpers::tryRead (d, size); - if (! optChunkSize.valid) + if (! optChunkSize.hasValue()) return false; - const auto chunkSize = optChunkSize.value; + const auto chunkSize = *optChunkSize; if (size < chunkSize) return false; - if (optChunkType.value == ByteOrder::bigEndianInt ("MTrk")) + if (*optChunkType == ByteOrder::bigEndianInt ("MTrk")) readNextTrack (d, (int) chunkSize, createMatchingNoteOffs); size -= chunkSize; @@ -610,7 +598,7 @@ struct MidiFileTest : public UnitTest { // No data const auto header = parseHeader ([] (OutputStream&) {}); - expect (! header.valid); + expect (! header.hasValue()); } { @@ -620,7 +608,7 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 0xff }); }); - expect (! header.valid); + expect (! header.hasValue()); } { @@ -630,7 +618,7 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'h', 'd' }); }); - expect (! header.valid); + expect (! header.hasValue()); } { @@ -640,7 +628,7 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 }); }); - expect (! header.valid); + expect (! header.hasValue()); } { @@ -650,7 +638,7 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 }); }); - expect (! header.valid); + expect (! header.hasValue()); } { @@ -660,12 +648,12 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 }); }); - expect (header.valid); + expect (header.hasValue()); - expectEquals (header.value.fileType, (short) 1); - expectEquals (header.value.numberOfTracks, (short) 16); - expectEquals (header.value.timeFormat, (short) 1); - expectEquals ((int) header.value.bytesRead, 14); + expectEquals (header->fileType, (short) 1); + expectEquals (header->numberOfTracks, (short) 16); + expectEquals (header->timeFormat, (short) 1); + expectEquals ((int) header->bytesRead, 14); } } @@ -674,7 +662,7 @@ struct MidiFileTest : public UnitTest { // Empty input const auto file = parseFile ([] (OutputStream&) {}); - expect (! file.valid); + expect (! file.hasValue()); } { @@ -684,7 +672,7 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'h', 'd' }); }); - expect (! file.valid); + expect (! file.hasValue()); } { @@ -694,8 +682,8 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 }); }); - expect (file.valid); - expectEquals (file.value.getNumTracks(), 0); + expect (file.hasValue()); + expectEquals (file->getNumTracks(), 0); } { @@ -706,7 +694,7 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'r', '?' }); }); - expect (! file.valid); + expect (! file.hasValue()); } { @@ -717,9 +705,9 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff }); }); - expect (file.valid); - expectEquals (file.value.getNumTracks(), 1); - expectEquals (file.value.getTrack (0)->getNumEvents(), 0); + expect (file.hasValue()); + expectEquals (file->getNumTracks(), 1); + expectEquals (file->getTrack (0)->getNumEvents(), 0); } { @@ -730,7 +718,7 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff }); }); - expect (! file.valid); + expect (! file.hasValue()); } { @@ -744,10 +732,10 @@ struct MidiFileTest : public UnitTest writeBytes (os, { 0x80, 0x00, 0x00 }); }); - expect (file.valid); - expectEquals (file.value.getNumTracks(), 1); + expect (file.hasValue()); + expectEquals (file->getNumTracks(), 1); - auto& track = *file.value.getTrack (0); + auto& track = *file->getTrack (0); expectEquals (track.getNumEvents(), 1); expect (track.getEventPointer (0)->message.isNoteOff()); expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f); @@ -766,7 +754,7 @@ struct MidiFileTest : public UnitTest } template - static MidiFileHelpers::Optional parseHeader (Fn&& fn) + static Optional parseHeader (Fn&& fn) { MemoryOutputStream os; fn (os); @@ -776,7 +764,7 @@ struct MidiFileTest : public UnitTest } template - static MidiFileHelpers::Optional parseFile (Fn&& fn) + static Optional parseFile (Fn&& fn) { MemoryOutputStream os; fn (os); diff --git a/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index 93358e5165..129ad9e085 100644 --- a/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -306,63 +306,56 @@ void MidiMessageSequence::deleteSysExMessages() //============================================================================== class OptionalPitchWheel { - int value = 0; - bool valid = false; + Optional value; public: void emit (int channel, Array& out) const { - if (valid) - out.add (MidiMessage::pitchWheel (channel, value)); + if (value.hasValue()) + out.add (MidiMessage::pitchWheel (channel, *value)); } void set (int v) { value = v; - valid = true; } }; class OptionalControllerValues { - int values[128]; + Optional values[128]; public: - OptionalControllerValues() - { - std::fill (std::begin (values), std::end (values), -1); - } - void emit (int channel, Array& out) const { for (auto it = std::begin (values); it != std::end (values); ++it) - if (*it != -1) - out.add (MidiMessage::controllerEvent (channel, (int) std::distance (std::begin (values), it), *it)); + if (it->hasValue()) + out.add (MidiMessage::controllerEvent (channel, (int) std::distance (std::begin (values), it), **it)); } void set (int controller, int value) { - values[controller] = value; + values[controller] = (char) value; } }; class OptionalProgramChange { - int value = -1, bankLSB = -1, bankMSB = -1; + Optional value, bankLSB, bankMSB; public: void emit (int channel, double time, Array& out) const { - if (value == -1) + if (! value.hasValue()) return; - if (bankLSB != -1 && bankMSB != -1) + if (bankLSB.hasValue() && bankMSB.hasValue()) { - out.add (MidiMessage::controllerEvent (channel, 0x00, bankMSB).withTimeStamp (time)); - out.add (MidiMessage::controllerEvent (channel, 0x20, bankLSB).withTimeStamp (time)); + out.add (MidiMessage::controllerEvent (channel, 0x00, *bankMSB).withTimeStamp (time)); + out.add (MidiMessage::controllerEvent (channel, 0x20, *bankLSB).withTimeStamp (time)); } - out.add (MidiMessage::programChange (channel, value).withTimeStamp (time)); + out.add (MidiMessage::programChange (channel, *value).withTimeStamp (time)); } // Returns true if this is a bank number change, and false otherwise. @@ -370,22 +363,21 @@ public: { switch (controller) { - case 0x00: bankMSB = v; return true; - case 0x20: bankLSB = v; return true; + case 0x00: bankMSB = (char) v; return true; + case 0x20: bankLSB = (char) v; return true; } return false; } - void setProgram (int v) { value = v; } + void setProgram (int v) { value = (char) v; } }; class ParameterNumberState { enum class Kind { rpn, nrpn }; - int newestRpnLsb = -1, newestRpnMsb = -1, newestNrpnLsb = -1, newestNrpnMsb = -1; - int lastSentLsb = -1, lastSentMsb = -1; + Optional newestRpnLsb, newestRpnMsb, newestNrpnLsb, newestNrpnMsb, lastSentLsb, lastSentMsb; Kind lastSentKind = Kind::rpn, newestKind = Kind::rpn; public: @@ -401,11 +393,11 @@ public: auto lastSent = std::tie (lastSentKind, lastSentMsb, lastSentLsb); const auto newest = std::tie (newestKind, newestMsb, newestLsb); - if (lastSent == newest || newestMsb == -1 || newestLsb == -1) + if (lastSent == newest || ! newestMsb.hasValue() || ! newestLsb.hasValue()) return; - out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x65 : 0x63, newestMsb).withTimeStamp (time)); - out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x64 : 0x62, newestLsb).withTimeStamp (time)); + out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x65 : 0x63, *newestMsb).withTimeStamp (time)); + out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x64 : 0x62, *newestLsb).withTimeStamp (time)); lastSent = newest; } @@ -415,10 +407,10 @@ public: { switch (controller) { - case 0x65: newestRpnMsb = value; newestKind = Kind::rpn; return true; - case 0x64: newestRpnLsb = value; newestKind = Kind::rpn; return true; - case 0x63: newestNrpnMsb = value; newestKind = Kind::nrpn; return true; - case 0x62: newestNrpnLsb = value; newestKind = Kind::nrpn; return true; + case 0x65: newestRpnMsb = (char) value; newestKind = Kind::rpn; return true; + case 0x64: newestRpnLsb = (char) value; newestKind = Kind::rpn; return true; + case 0x63: newestNrpnMsb = (char) value; newestKind = Kind::nrpn; return true; + case 0x62: newestNrpnLsb = (char) value; newestKind = Kind::nrpn; return true; } return false; diff --git a/modules/juce_core/containers/juce_Optional.h b/modules/juce_core/containers/juce_Optional.h new file mode 100644 index 0000000000..f7539fb05d --- /dev/null +++ b/modules/juce_core/containers/juce_Optional.h @@ -0,0 +1,378 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +#include + +namespace juce +{ + +namespace detail +{ +namespace adlSwap +{ +using std::swap; + +template +constexpr auto isNothrowSwappable = noexcept (swap (std::declval(), std::declval())); +} // namespace adlSwap +} // namespace detail + +struct Nullopt {}; + +constexpr Nullopt nullopt; + +// Without this, our tests can emit "unreachable code" warnings during +// link time code generation. +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702) + +/* For internal use only! + + A simple optional type. + + Has similar (not necessarily identical!) semantics to std::optional. + + This isn't really intended to be used by JUCE clients. Instead, it's to be + used internally in JUCE code, with an API close-enough to std::optional + that the types can be swapped with fairly minor disruption at some point in + the future, but *without breaking any public APIs*. +*/ +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>; + +public: + Optional() = default; + + Optional (Nullopt) noexcept {} + + template ::value + && ! std::is_same, Optional>::value>> + Optional (U&& value) noexcept (noexcept (Value (std::forward (value)))) + : valid (true) + { + new (&storage) Value (std::forward (value)); + } + + Optional (Optional&& other) noexcept (noexcept (std::declval().constructFrom (other))) + { + constructFrom (other); + } + + Optional (const Optional& other) + : valid (other.valid) + { + if (valid) + new (&storage) Value (*other); + } + + template > + Optional (Optional&& other) noexcept (noexcept (std::declval().constructFrom (other))) + { + constructFrom (other); + } + + template > + Optional (const Optional& other) + : valid (other.hasValue()) + { + if (valid) + new (&storage) Value (*other); + } + + Optional& operator= (Nullopt) noexcept + { + reset(); + 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 > + Optional& operator= (const Optional& other) + { + auto copy = other; + assign (copy); + return *this; + } + + ~Optional() noexcept + { + reset(); + } + + 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() + { + if (std::exchange (valid, false)) + operator*().~Value(); + } + + /* Like std::optional::value_or */ + template + Value orFallback (U&& fallback) const { return *this ? **this : std::forward (fallback); } + + template + Value& emplace (Args&&... args) + { + reset(); + new (&storage) Value (std::forward (args)...); + valid = true; + return **this; + } + + 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); + } + } + +private: + template + void constructFrom (Optional& other) noexcept (noexcept (Value (std::move (*other)))) + { + if (! other.hasValue()) + return; + + 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); + } + } + + std::aligned_storage_t storage; + bool valid = false; +}; + +JUCE_END_IGNORE_WARNINGS_MSVC + +template +bool operator== (const Optional& lhs, const Optional& rhs) +{ + if (lhs.hasValue() != rhs.hasValue()) return false; + if (! lhs.hasValue()) return true; + return *lhs == *rhs; +} + +template +bool operator!= (const Optional& lhs, const Optional& rhs) +{ + if (lhs.hasValue() != rhs.hasValue()) return true; + if (! lhs.hasValue()) return false; + return *lhs != *rhs; +} + +template +bool operator< (const Optional& lhs, const Optional& rhs) +{ + if (! rhs.hasValue()) return false; + if (! lhs.hasValue()) return true; + return *lhs < *rhs; +} + +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; } + +} // namespace juce diff --git a/modules/juce_core/containers/juce_Optional_test.cpp b/modules/juce_core/containers/juce_Optional_test.cpp new file mode 100644 index 0000000000..cc2b7994a8 --- /dev/null +++ b/modules/juce_core/containers/juce_Optional_test.cpp @@ -0,0 +1,627 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/* Not nested, so that ADL works for the swap function. */ +struct ThrowOnMoveOrSwap +{ + ThrowOnMoveOrSwap() = default; + ThrowOnMoveOrSwap (ThrowOnMoveOrSwap&&) { throw std::bad_alloc{}; } +}; +static void swap (ThrowOnMoveOrSwap&, ThrowOnMoveOrSwap&) { throw std::bad_alloc{}; } + +class OptionalUnitTest : public UnitTest +{ +public: + OptionalUnitTest() : UnitTest ("Optional", UnitTestCategories::containers) {} + + void runTest() override + { + beginTest ("Default-constructed optional is invalid"); + { + Optional o; + expect (! o.hasValue()); + } + + beginTest ("Constructing from Nullopt is invalid"); + { + Optional o (nullopt); + expect (! o.hasValue()); + } + + beginTest ("Optional constructed from value is valid"); + { + Optional o = 5; + expect (o.hasValue()); + expectEquals (*o, 5); + } + + using Ptr = std::shared_ptr; + const auto makePtr = [] { return std::make_shared(); }; + + beginTest ("Constructing from a moved optional calls appropriate member functions"); + { + auto ptr = makePtr(); + Optional original (ptr); + expect (ptr.use_count() == 2); + auto other = std::move (original); + expect (! original.hasValue()); + expect (other.hasValue()); + expect (ptr.use_count() == 2); + } + + beginTest ("Moving an empty optional to a populated one destroys the instance"); + { + auto ptr = makePtr(); + Optional original (ptr); + expect (ptr.use_count() == 2); + original = Optional(); + expect (ptr.use_count() == 1); + } + + beginTest ("Copying an empty optional to a populated one destroys the instance"); + { + auto ptr = makePtr(); + Optional original (ptr); + expect (ptr.use_count() == 2); + Optional empty; + original = empty; + expect (ptr.use_count() == 1); + } + + beginTest ("Moving a populated optional calls appropriate member functions"); + { + auto a = makePtr(); + auto b = makePtr(); + + Optional aOpt (a); + Optional bOpt (b); + + expect (a.use_count() == 2); + expect (b.use_count() == 2); + + aOpt = std::move (bOpt); + + expect (aOpt.hasValue()); + expect (! bOpt.hasValue()); + + expect (a.use_count() == 1); + expect (b.use_count() == 2); + } + + beginTest ("Copying a populated optional calls appropriate member functions"); + { + auto a = makePtr(); + auto b = makePtr(); + + Optional aOpt (a); + Optional bOpt (b); + + expect (a.use_count() == 2); + expect (b.use_count() == 2); + + aOpt = bOpt; + + expect (aOpt.hasValue()); + expect (bOpt.hasValue()); + + expect (a.use_count() == 1); + expect (b.use_count() == 3); + } + + beginTest ("Moving an empty optional to an empty one does nothing"); + { + Optional original; + original = Optional(); + expect (! original.hasValue()); + } + + beginTest ("Copying an empty optional to an empty one does nothing"); + { + Optional original; + Optional empty; + original = empty; + expect (! original.hasValue()); + expect (! empty.hasValue()); + } + + beginTest ("Moving a populated optional calls appropriate member functions"); + { + auto a = makePtr(); + + Optional aOpt (a); + Optional empty; + + expect (a.use_count() == 2); + + empty = std::move (aOpt); + + expect (empty.hasValue()); + expect (! aOpt.hasValue()); + + expect (a.use_count() == 2); + } + + beginTest ("Copying a populated optional calls appropriate member functions"); + { + auto a = makePtr(); + + Optional aOpt (a); + Optional empty; + + expect (a.use_count() == 2); + + empty = aOpt; + + expect (aOpt.hasValue()); + expect (empty.hasValue()); + + expect (a.use_count() == 3); + } + + struct ThrowOnCopy + { + ThrowOnCopy() = default; + + // Put into an invalid state and throw + ThrowOnCopy (const ThrowOnCopy&) + { + value = -100; + throw std::bad_alloc{}; + } + + // Put into an invalid state and throw + ThrowOnCopy& operator= (const ThrowOnCopy&) + { + value = -100; + throw std::bad_alloc{}; + } + + ThrowOnCopy (ThrowOnCopy&&) noexcept = default; + ThrowOnCopy& operator= (ThrowOnCopy&&) noexcept = default; + + ~ThrowOnCopy() = default; + + int value = 0; + }; + + beginTest ("Strong exception safety is maintained when forwarding over empty object"); + { + bool threw = false; + Optional a; + + try + { + ThrowOnCopy t; + a = t; + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (! a.hasValue()); // If construction failed, this object should still be well-formed but empty + } + + beginTest ("Weak exception safety is maintained when forwarding over populated object"); + { + bool threw = false; + Optional a = ThrowOnCopy(); + a->value = 5; + + try + { + ThrowOnCopy t; + a = t; + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (a.hasValue()); + expect (a->value == -100); // If we assign to an extant object, it's up to that object to provide an exception guarantee + } + + beginTest ("Strong exception safety is maintained when copying over empty object"); + { + bool threw = false; + Optional a; + + try + { + Optional t = ThrowOnCopy{}; + a = t; + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (! a.hasValue()); + } + + beginTest ("Strong exception safety is maintained when copying over populated object"); + { + bool threw = false; + Optional a = ThrowOnCopy(); + a->value = 5; + + try + { + Optional t = ThrowOnCopy{}; + a = t; + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (a.hasValue()); + expect (a->value == 5); + } + + beginTest ("Assigning from nullopt clears the instance"); + { + auto ptr = makePtr(); + Optional a (ptr); + expect (ptr.use_count() == 2); + a = nullopt; + expect (ptr.use_count() == 1); + } + + struct Foo {}; + struct Bar : Foo {}; + + beginTest ("Can be constructed from compatible type"); + { + Optional> opt { std::make_shared() }; + } + + beginTest ("Can be assigned from compatible type"); + { + Optional> opt; + opt = std::make_shared(); + } + + beginTest ("Can copy from compatible type"); + { + auto ptr = std::make_shared(); + Optional> bar (ptr); + Optional> foo (bar); + expect (ptr.use_count() == 3); + } + + beginTest ("Can move from compatible type"); + { + auto ptr = std::make_shared(); + Optional> foo (Optional> { ptr }); + expect (ptr.use_count() == 2); + } + + beginTest ("Can copy assign from compatible type"); + { + auto ptr = std::make_shared(); + Optional> bar (ptr); + Optional> foo; + foo = bar; + expect (ptr.use_count() == 3); + } + + beginTest ("Can move assign from compatible type"); + { + auto ptr = std::make_shared(); + Optional> foo; + foo = Optional> (ptr); + expect (ptr.use_count() == 2); + } + + beginTest ("An exception thrown during emplace leaves the optional without a value"); + { + Optional opt { ThrowOnCopy{} }; + bool threw = false; + + try + { + ThrowOnCopy t; + opt.emplace (t); + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (! opt.hasValue()); + } + + beginTest ("Swap does nothing to two empty optionals"); + { + Optional a, b; + expect (! a.hasValue()); + expect (! b.hasValue()); + + a.swap (b); + + expect (! a.hasValue()); + expect (! b.hasValue()); + } + + beginTest ("Swap transfers ownership if one optional contains a value"); + { + { + Ptr ptr = makePtr(); + Optional a, b = ptr; + expect (! a.hasValue()); + expect (b.hasValue()); + expect (ptr.use_count() == 2); + + a.swap (b); + + expect (a.hasValue()); + expect (! b.hasValue()); + expect (ptr.use_count() == 2); + } + + { + auto ptr = makePtr(); + Optional a = ptr, b; + expect (a.hasValue()); + expect (! b.hasValue()); + expect (ptr.use_count() == 2); + + a.swap (b); + + expect (! a.hasValue()); + expect (b.hasValue()); + expect (ptr.use_count() == 2); + } + } + + beginTest ("Swap calls std::swap to swap two populated optionals"); + { + auto x = makePtr(), y = makePtr(); + Optional a = x, b = y; + expect (a.hasValue()); + expect (b.hasValue()); + expect (x.use_count() == 2); + expect (y.use_count() == 2); + expect (*a == x); + expect (*b == y); + + a.swap (b); + + expect (a.hasValue()); + expect (b.hasValue()); + expect (x.use_count() == 2); + expect (y.use_count() == 2); + expect (*a == y); + expect (*b == x); + } + + beginTest ("An exception thrown during a swap leaves both objects in the previous populated state"); + { + { + Optional a, b; + a.emplace(); + + expect (a.hasValue()); + expect (! b.hasValue()); + + bool threw = false; + + try + { + a.swap (b); + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (a.hasValue()); + expect (! b.hasValue()); + } + + { + Optional a, b; + b.emplace(); + + expect (! a.hasValue()); + expect (b.hasValue()); + + bool threw = false; + + try + { + a.swap (b); + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (! a.hasValue()); + expect (b.hasValue()); + } + + { + Optional a, b; + a.emplace(); + b.emplace(); + + expect (a.hasValue()); + expect (b.hasValue()); + + bool threw = false; + + try + { + a.swap (b); + } + catch (const std::bad_alloc&) + { + threw = true; + } + + expect (threw); + expect (a.hasValue()); + expect (b.hasValue()); + } + } + + beginTest ("Relational tests"); + { + expect (Optional (1) == Optional (1)); + expect (Optional() == Optional()); + expect (! (Optional (1) == Optional())); + expect (! (Optional() == Optional (1))); + expect (! (Optional (1) == Optional (2))); + + expect (Optional (1) != Optional (2)); + expect (! (Optional() != Optional())); + expect (Optional (1) != Optional()); + expect (Optional() != Optional (1)); + expect (! (Optional (1) != Optional (1))); + + expect (Optional() < Optional (1)); + expect (! (Optional (1) < Optional())); + expect (! (Optional() < Optional())); + expect (Optional (1) < Optional (2)); + + expect (Optional() <= Optional (1)); + expect (! (Optional (1) <= Optional())); + expect (Optional() <= Optional()); + expect (Optional (1) <= Optional (2)); + + expect (! (Optional() > Optional (1))); + expect (Optional (1) > Optional()); + expect (! (Optional() > Optional())); + expect (! (Optional (1) > Optional (2))); + + expect (! (Optional() >= Optional (1))); + expect (Optional (1) >= Optional()); + expect (Optional() >= Optional()); + expect (! (Optional (1) >= Optional (2))); + + expect (Optional() == nullopt); + expect (! (Optional (1) == nullopt)); + expect (nullopt == Optional()); + expect (! (nullopt == Optional (1))); + + expect (! (Optional() != nullopt)); + expect (Optional (1) != nullopt); + expect (! (nullopt != Optional())); + expect (nullopt != Optional (1)); + + expect (! (Optional() < nullopt)); + expect (! (Optional (1) < nullopt)); + + expect (! (nullopt < Optional())); + expect (nullopt < Optional (1)); + + expect (Optional() <= nullopt); + expect (! (Optional (1) <= nullopt)); + + expect (nullopt <= Optional()); + expect (nullopt <= Optional (1)); + + expect (! (Optional() > nullopt)); + expect (Optional (1) > nullopt); + + expect (! (nullopt > Optional())); + expect (! (nullopt > Optional (1))); + + expect (Optional() >= nullopt); + expect (Optional (1) >= nullopt); + + expect (nullopt >= Optional()); + expect (! (nullopt >= Optional (1))); + + expect (! (Optional() == 5)); + expect (! (Optional (1) == 5)); + expect (Optional (1) == 1); + expect (! (5 == Optional())); + expect (! (5 == Optional (1))); + expect (1 == Optional (1)); + + expect (Optional() != 5); + expect (Optional (1) != 5); + expect (! (Optional (1) != 1)); + expect (5 != Optional()); + expect (5 != Optional (1)); + expect (! (1 != Optional (1))); + + expect (Optional() < 5); + expect (Optional (1) < 5); + expect (! (Optional (1) < 1)); + expect (! (Optional (1) < 0)); + + expect (! (5 < Optional())); + expect (! (5 < Optional (1))); + expect (! (1 < Optional (1))); + expect (0 < Optional (1)); + + expect (Optional() <= 5); + expect (Optional (1) <= 5); + expect (Optional (1) <= 1); + expect (! (Optional (1) <= 0)); + + expect (! (5 <= Optional())); + expect (! (5 <= Optional (1))); + expect (1 <= Optional (1)); + expect (0 <= Optional (1)); + + expect (! (Optional() > 5)); + expect (! (Optional (1) > 5)); + expect (! (Optional (1) > 1)); + expect (Optional (1) > 0); + + expect (5 > Optional()); + expect (5 > Optional (1)); + expect (! (1 > Optional (1))); + expect (! (0 > Optional (1))); + + expect (! (Optional() >= 5)); + expect (! (Optional (1) >= 5)); + expect (Optional (1) >= 1); + expect (Optional (1) >= 0); + + expect (5 >= Optional()); + expect (5 >= Optional (1)); + expect (1 >= Optional (1)); + expect (! (0 >= Optional (1))); + } + } +}; + +static OptionalUnitTest optionalUnitTest; + +} // namespace juce diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index 24444c000c..aa1f72ff62 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -261,6 +261,9 @@ //============================================================================== #if JUCE_UNIT_TESTS #include "containers/juce_HashMap_test.cpp" + + #include "containers/juce_Optional.h" + #include "containers/juce_Optional_test.cpp" #endif //==============================================================================