diff options
Diffstat (limited to 'lib/util.h')
-rw-r--r-- | lib/util.h | 148 |
1 files changed, 79 insertions, 69 deletions
@@ -24,6 +24,7 @@ #include <functional> #include <memory> #include <unordered_map> +#include <optional> // Along the lines of Q_DISABLE_COPY - the upstream version comes in Qt 5.13 #define DISABLE_MOVE(_ClassName) \ @@ -43,68 +44,83 @@ struct HashQ { template <typename KeyT, typename ValT> using UnorderedMap = std::unordered_map<KeyT, ValT, HashQ<KeyT>>; -struct NoneTag {}; -constexpr NoneTag none {}; +constexpr auto none = std::nullopt; -/** A crude substitute for `optional` while we're not C++17 +/** `std::optional` with tweaks * - * Only works with default-constructible types. + * The tweaks are: + * - streamlined assignment (operator=)/emplace()ment of values that can be + * used to implicitly construct the underlying type, including + * direct-list-initialisation, e.g.: + * \code + * struct S { int a; char b; } + * Omittable<S> o; + * o = { 1, 'a' }; // std::optional would require o = S { 1, 'a' } + * \endcode + * - entirely deleted value(). The technical reason is that Xcode 10 doesn't + * have it; but besides that, value_or() or (after explicit checking) + * `operator*()`/`operator->()` are better alternatives within Quotient + * that doesn't practice throwing exceptions (as doesn't most of Qt). + * - disabled non-const lvalue operator*() and operator->(), as it's too easy + * to inadvertently cause a value change through them. + * - edit() to provide a safe and explicit lvalue accessor instead of those + * above. Requires the underlying type to be default-constructible. + * Allows chained initialisation of nested Omittables: + * \code + * struct Inner { int member = 10; Omittable<int> innermost; }; + * struct Outer { int anotherMember = 10; Omittable<Inner> inner; }; + * Omittable<Outer> o; // = { 10, std::nullopt }; + * o.edit().inner.edit().innermost.emplace(42); + * \endcode + * - merge() - a soft version of operator= that only overwrites its first + * operand with the second one if the second one is not empty. */ template <typename T> -class Omittable { - static_assert(!std::is_reference<T>::value, - "You cannot make an Omittable<> with a reference type"); - +class Omittable : public std::optional<T> { public: + using base_type = std::optional<T>; using value_type = std::decay_t<T>; - explicit Omittable() : Omittable(none) {} - Omittable(NoneTag) : _value(value_type()), _omitted(true) {} - Omittable(const value_type& val) : _value(val) {} - Omittable(value_type&& val) : _value(std::move(val)) {} - Omittable<T>& operator=(const value_type& val) + using std::optional<T>::optional; + + // Overload emplace() and operator=() to allow passing braced-init-lists + // (the standard emplace() does direct-initialisation but + // not direct-list-initialisation). + using base_type::operator=; + Omittable& operator=(const value_type& v) { - _value = val; - _omitted = false; + base_type::operator=(v); return *this; } - Omittable<T>& operator=(value_type&& val) + Omittable& operator=(value_type&& v) { - // For some reason GCC complains about -Wmaybe-uninitialized - // in the context of using Omittable<bool> with converters.h; - // though the logic looks very much benign (GCC bug???) - _value = std::move(val); - _omitted = false; + base_type::operator=(v); return *this; } - - bool operator==(const value_type& rhs) const - { - return !omitted() && value() == rhs; - } - friend bool operator==(const value_type& lhs, - const Omittable<value_type>& rhs) - { - return rhs == lhs; - } - bool operator!=(const value_type& rhs) const { return !operator==(rhs); } - friend bool operator!=(const value_type& lhs, - const Omittable<value_type>& rhs) + using base_type::emplace; + T& emplace(const T& val) { return base_type::emplace(val); } + T& emplace(T&& val) { return base_type::emplace(std::move(val)); } + + // use value_or() or check (with operator! or has_value) before accessing + // with operator-> or operator* + // The technical reason is that Xcode 10 has incomplete std::optional + // that has no value(); but using value() may also mean that you rely + // on the optional throwing an exception (which is not assumed practice + // throughout Quotient) or that you spend unnecessary CPU cycles on + // an extraneous has_value() check. + value_type& value() = delete; + const value_type& value() const = delete; + value_type& edit() { - return !(rhs == lhs); + return this->has_value() ? base_type::operator*() : this->emplace(); } - bool omitted() const { return _omitted; } - const value_type& value() const + [[deprecated("Use '!o' or '!o.has_value()' instead of 'o.omitted()'")]] + bool omitted() const { - Q_ASSERT(!_omitted); - return _value; - } - value_type& editValue() - { - _omitted = false; - return _value; + return !this->has_value(); } + /// Merge the value from another Omittable /// \return true if \p other is not omitted and the value of /// the current Omittable was different (or omitted); @@ -114,26 +130,20 @@ public: auto merge(const Omittable<T1>& other) -> std::enable_if_t<std::is_convertible<T1, T>::value, bool> { - if (other.omitted() || (!_omitted && _value == other.value())) + if (!other || (this->has_value() && **this == *other)) return false; - _omitted = false; - _value = other.value(); + *this = other; return true; } - value_type&& release() - { - _omitted = true; - return std::move(_value); - } - const value_type* operator->() const& { return &value(); } - value_type* operator->() & { return &editValue(); } - const value_type& operator*() const& { return value(); } - value_type& operator*() & { return editValue(); } + // Hide non-const lvalue operator-> and operator* as these are + // a bit too surprising: value() & doesn't lazy-create an object; + // and it's too easy to inadvertently change the underlying value. -private: - T _value; - bool _omitted = false; + const value_type* operator->() const& { return base_type::operator->(); } + value_type* operator->() && { return base_type::operator->(); } + const value_type& operator*() const& { return base_type::operator*(); } + value_type& operator*() && { return base_type::operator*(); } }; namespace _impl { @@ -213,19 +223,19 @@ class Range { using size_type = typename ArrayT::size_type; public: - Range(ArrayT& arr) : from(std::begin(arr)), to(std::end(arr)) {} - Range(iterator from, iterator to) : from(from), to(to) {} + constexpr Range(ArrayT& arr) : from(std::begin(arr)), to(std::end(arr)) {} + constexpr Range(iterator from, iterator to) : from(from), to(to) {} - size_type size() const + constexpr size_type size() const { Q_ASSERT(std::distance(from, to) >= 0); return size_type(std::distance(from, to)); } - bool empty() const { return from == to; } - const_iterator begin() const { return from; } - const_iterator end() const { return to; } - iterator begin() { return from; } - iterator end() { return to; } + constexpr bool empty() const { return from == to; } + constexpr const_iterator begin() const { return from; } + constexpr const_iterator end() const { return to; } + constexpr iterator begin() { return from; } + constexpr iterator end() { return to; } private: iterator from; @@ -239,8 +249,8 @@ private: */ template <typename InputIt, typename ForwardIt, typename Pred> inline std::pair<InputIt, ForwardIt> findFirstOf(InputIt first, InputIt last, - ForwardIt sFirst, - ForwardIt sLast, Pred pred) + ForwardIt sFirst, + ForwardIt sLast, Pred pred) { for (; first != last; ++first) for (auto it = sFirst; it != sLast; ++it) |