diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | gtad/gtad.yaml | 2 | ||||
-rw-r--r-- | lib/connection.h | 1 | ||||
-rw-r--r-- | lib/converters.h | 1 | ||||
-rw-r--r-- | lib/omittable.cpp | 34 | ||||
-rw-r--r-- | lib/omittable.h | 223 | ||||
-rw-r--r-- | lib/util.h | 141 |
7 files changed, 261 insertions, 142 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 599424ab..13c8f591 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ list(APPEND lib_SRCS lib/quotient_common.h lib/quotient_export.h lib/function_traits.h lib/function_traits.cpp + lib/omittable.h lib/omittable.cpp lib/networkaccessmanager.h lib/networkaccessmanager.cpp lib/connectiondata.h lib/connectiondata.cpp lib/connection.h lib/connection.cpp diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml index 943ac013..03c23886 100644 --- a/gtad/gtad.yaml +++ b/gtad/gtad.yaml @@ -45,7 +45,7 @@ analyzer: types: - +set: &UseOmittable useOmittable: - omittedValue: 'none' # Quotient::none in lib/util.h + omittedValue: 'none' # Quotient::none in lib/omittable.h +on: - integer: - int64: qint64 diff --git a/lib/connection.h b/lib/connection.h index 28688cc1..dc2eaad1 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -8,6 +8,7 @@ #include "ssosession.h" #include "qt_connection_util.h" #include "quotient_common.h" +#include "util.h" #include "csapi/login.h" #include "csapi/create_room.h" diff --git a/lib/converters.h b/lib/converters.h index 8eea1cd3..e7bc6898 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -3,6 +3,7 @@ #pragma once +#include "omittable.h" #include "util.h" #include <QtCore/QDate> diff --git a/lib/omittable.cpp b/lib/omittable.cpp new file mode 100644 index 00000000..245ae721 --- /dev/null +++ b/lib/omittable.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "omittable.h" + +// Omittable<> tests +using namespace Quotient; + +Omittable<int> testFn(bool) { return 0; } +bool testFn2(int) { return false; } +static_assert( + std::is_same_v<decltype(std::declval<Omittable<bool>>().then(testFn)), + Omittable<int>>); +static_assert( + std::is_same_v< + decltype(std::declval<Omittable<bool>>().then_or(testFn, 0)), int>); +static_assert( + std::is_same_v<decltype(std::declval<Omittable<bool>>().then(testFn)), + Omittable<int>>); +static_assert(std::is_same_v<decltype(std::declval<Omittable<int>>() + .then(testFn2) + .then(testFn)), + Omittable<int>>); +static_assert(std::is_same_v<decltype(std::declval<Omittable<bool>>() + .then(testFn) + .then_or(testFn2, false)), + bool>); + +constexpr auto visitTestFn(int, bool) { return false; } +static_assert( + std::is_same_v<Omittable<bool>, decltype(lift(testFn2, Omittable<int>()))>); +static_assert(std::is_same_v<Omittable<bool>, + decltype(lift(visitTestFn, Omittable<int>(), + Omittable<bool>()))>); diff --git a/lib/omittable.h b/lib/omittable.h new file mode 100644 index 00000000..b5efecf5 --- /dev/null +++ b/lib/omittable.h @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include <optional> +#include <functional> + +namespace Quotient { + +template <typename T> +class Omittable; + +constexpr auto none = std::nullopt; + +//! \brief Lift an operation into dereferenceable types (Omittables or pointers) +//! +//! This is a more generic version of Omittable::then() that extends to +//! an arbitrary number of arguments of any type that is dereferenceable (unary +//! operator*() can be applied to it) and (explicitly or implicitly) convertible +//! to bool. This allows to streamline checking for nullptr/none before applying +//! the operation on the underlying types. \p fn is only invoked if all \p args +//! are "truthy" (i.e. <tt>(... && bool(args)) == true</tt>). +//! \param fn A callable that should accept the types stored inside +//! Omittables/pointers passed in \p args +//! \return Always an Omittable: if \p fn returns another type, lift() wraps +//! it in an Omittable; if \p fn returns an Omittable, that return value +//! (or none) is returned as is. +template <typename FnT, typename... MaybeTs> +inline auto lift(FnT&& fn, MaybeTs&&... args) +{ + return (... && bool(args)) + ? Omittable(std::invoke(std::forward<FnT>(fn), *args...)) + : none; +} + +/** `std::optional` with tweaks + * + * 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. + * - ensure() to provide a safe and explicit lvalue accessor instead of + * those above. 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.ensure().inner.ensure().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. + * - then() and then_or() to streamline read-only interrogation in a "monadic" + * interface. + */ +template <typename T> +class Omittable : public std::optional<T> { +public: + using base_type = std::optional<T>; + using value_type = std::decay_t<T>; + + 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) + { + base_type::operator=(v); + return *this; + } + Omittable& operator=(value_type&& v) + { + base_type::operator=(std::move(v)); + return *this; + } + + 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. + auto& value() = delete; + const auto& value() const = delete; + + template <typename U> + value_type& ensure(U&& defaultValue = value_type {}) + { + return this->has_value() ? this->operator*() + : this->emplace(std::forward<U>(defaultValue)); + } + value_type& ensure(const value_type& defaultValue) + { + return ensure<>(defaultValue); + } + value_type& ensure(value_type&& defaultValue) + { + return ensure<>(std::move(defaultValue)); + } + + //! 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), + //! in other words, if the current Omittable has changed; + //! false otherwise + template <typename T1> + auto merge(const std::optional<T1>& other) + -> std::enable_if_t<std::is_convertible_v<T1, T>, bool> + { + if (!other || (this->has_value() && **this == *other)) + return false; + this->emplace(*other); + return true; + } + + // 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. + + 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*(); } + + // The below is inspired by the proposed std::optional monadic operations + // (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0798r6.html). + + //! \brief Lift a callable into the Omittable + //! + //! 'Lifting', as used in functional programming, means here invoking + //! a callable (e.g., a function) on the contents of the Omittable if it has + //! any and wrapping the returned value (that may be of a different type T2) + //! into a new Omittable\<T2>. If the current Omittable is empty, + //! the invocation is skipped altogether and Omittable\<T2>{none} is + //! returned instead. + //! \note if \p fn already returns an Omittable (i.e., it is a 'functor', + //! in functional programming terms), then() will not wrap another + //! Omittable around but will just return what \p fn returns. The + //! same doesn't hold for the parameter: if \p fn accepts an Omittable + //! you have to wrap it in another Omittable before calling then(). + //! \return `none` if the current Omittable has `none`; + //! otherwise, the Omittable returned from a call to \p fn + //! \tparam FnT a callable with \p T (or <tt>const T&</tt>) + //! returning Omittable<T2>, T2 is any supported type + //! \sa then_or, transform + template <typename FnT> + auto then(FnT&& fn) const& + { + return lift(std::forward<FnT>(fn), *this); + } + + //! \brief Lift a callable into the rvalue Omittable + //! + //! This is an rvalue overload for then(). + template <typename FnT> + auto then(FnT&& fn) && + { + return lift(std::forward<FnT>(fn), *this); + } + + //! \brief Lift a callable into the const lvalue Omittable, with a fallback + //! + //! This effectively does the same what then() does, except that it returns + //! a value of type returned by the callable, or the provided fallback value + //! if the current Omittable is empty. This is a typesafe version to apply + //! an operation on an Omittable without having to deal with another + //! Omittable afterwards. + template <typename FnT, typename FallbackT> + auto then_or(FnT&& fn, FallbackT&& fallback) const& + { + return then(std::forward<FnT>(fn)) + .value_or(std::forward<FallbackT>(fallback)); + } + + //! \brief Lift a callable into the rvalue Omittable, with a fallback + //! + //! This is an overload for functions that accept rvalue + template <typename FnT, typename FallbackT> + auto then_or(FnT&& fn, FallbackT&& fallback) && + { + return then(std::forward<FnT>(fn)) + .value_or(std::forward<FallbackT>(fallback)); + } +}; + +template <typename T> +Omittable(T&&) -> Omittable<T>; + +//! \brief Merge the value from an optional +//! This is an adaptation of Omittable::merge() to the case when the value +//! on the left hand side is not an Omittable. +//! \return true if \p rhs is not omitted and the \p lhs value was different, +//! in other words, if \p lhs has changed; +//! false otherwise +template <typename T1, typename T2> +inline auto merge(T1& lhs, const std::optional<T2>& rhs) + -> std::enable_if_t<std::is_assignable_v<T1&, const T2&>, bool> +{ + if (!rhs || lhs == *rhs) + return false; + lhs = *rhs; + return true; +} + +} // namespace Quotient @@ -9,10 +9,8 @@ #include <QtCore/QLatin1String> #include <QtCore/QHashFunctions> -#include <functional> #include <memory> #include <unordered_map> -#include <optional> #ifndef Q_DISABLE_MOVE // Q_DISABLE_MOVE was introduced in Q_VERSION_CHECK(5,13,0) @@ -52,145 +50,6 @@ struct HashQ { template <typename KeyT, typename ValT> using UnorderedMap = std::unordered_map<KeyT, ValT, HashQ<KeyT>>; -namespace _impl { - template <typename TT> - constexpr auto IsOmittableValue = false; - template <typename TT> - constexpr auto IsOmittable = IsOmittableValue<std::decay_t<TT>>; -} - -constexpr auto none = std::nullopt; - -/** `std::optional` with tweaks - * - * 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 : public std::optional<T> { -public: - using base_type = std::optional<T>; - using value_type = std::decay_t<T>; - - 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) - { - base_type::operator=(v); - return *this; - } - Omittable& operator=(value_type&& v) - { - base_type::operator=(v); - return *this; - } - 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 this->has_value() ? base_type::operator*() : this->emplace(); - } - - [[deprecated("Use '!o' or '!o.has_value()' instead of 'o.omitted()'")]] - bool omitted() const - { - 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), - //! in other words, if the current Omittable has changed; - //! false otherwise - template <typename T1> - auto merge(const Omittable<T1>& other) - -> std::enable_if_t<std::is_convertible_v<T1, T>, bool> - { - if (!other || (this->has_value() && **this == *other)) - return false; - emplace(*other); - return true; - } - - // 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. - - 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*(); } -}; -template <typename T> -Omittable(T&&) -> Omittable<T>; - -namespace _impl { - template <typename T> - constexpr auto IsOmittableValue<Omittable<T>> = true; -} - -template <typename T1, typename T2> -inline auto merge(Omittable<T1>& lhs, T2&& rhs) -{ - return lhs.merge(std::forward<T2>(rhs)); -} - -//! \brief Merge the value from an Omittable -//! This is an adaptation of Omittable::merge() to the case when the value -//! on the left hand side is not an Omittable. -//! \return true if \p rhs is not omitted and the \p lhs value was different, -//! in other words, if \p lhs has changed; -//! false otherwise -template <typename T1, typename T2> -inline auto merge(T1& lhs, const Omittable<T2>& rhs) - -> std::enable_if_t<!_impl::IsOmittable<T1> - && std::is_convertible_v<T2, T1>, bool> -{ - if (!rhs || lhs == *rhs) - return false; - lhs = *rhs; - return true; -} - constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); |