aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--gtad/gtad.yaml2
-rw-r--r--lib/connection.h1
-rw-r--r--lib/converters.h1
-rw-r--r--lib/omittable.cpp34
-rw-r--r--lib/omittable.h223
-rw-r--r--lib/util.h141
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
diff --git a/lib/util.h b/lib/util.h
index 3505b62f..753eb1ea 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -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));