diff options
Diffstat (limited to 'lib/converters.h')
-rw-r--r-- | lib/converters.h | 702 |
1 files changed, 338 insertions, 364 deletions
diff --git a/lib/converters.h b/lib/converters.h index 36b7ff15..b753a80b 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -1,413 +1,387 @@ /****************************************************************************** -* Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; either -* version 2.1 of the License, or (at your option) any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ + * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ #pragma once #include "util.h" -#include <QtCore/QJsonObject> +#include <QtCore/QDate> #include <QtCore/QJsonArray> // Includes <QtCore/QJsonValue> #include <QtCore/QJsonDocument> -#include <QtCore/QDate> -#include <QtCore/QUrlQuery> +#include <QtCore/QJsonObject> #include <QtCore/QSet> +#include <QtCore/QUrlQuery> #include <QtCore/QVector> -#include <unordered_map> #include <vector> -#if 0 // Waiting for C++17 -#include <experimental/optional> - -template <typename T> -using optional = std::experimental::optional<T>; -#endif // Enable std::unordered_map<QString, T> -namespace std -{ - template <> struct hash<QString> +// REMOVEME in favor of UnorderedMap, once we regenerate API files +namespace std { +template <> +struct hash<QString> { + size_t operator()(const QString& s) const Q_DECL_NOEXCEPT { - size_t operator()(const QString& s) const Q_DECL_NOEXCEPT - { - return qHash(s + return qHash(s #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - , uint(qGlobalQHashSeed()) + , + uint(qGlobalQHashSeed()) #endif - ); - } - }; -} + ); + } +}; +} // namespace std class QVariant; -namespace QMatrixClient -{ - template <typename T> - struct JsonObjectConverter - { - static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod.toJson(); } - static void fillFrom(const QJsonObject& jo, T& pod) { pod = T(jo); } - }; +namespace Quotient { +template <typename T> +struct JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod.toJson(); } + static void fillFrom(const QJsonObject& jo, T& pod) { pod = T(jo); } +}; - template <typename T> - struct JsonConverter +template <typename T> +struct JsonConverter { + static QJsonObject dump(const T& pod) { - static QJsonObject dump(const T& pod) - { - QJsonObject jo; - JsonObjectConverter<T>::dumpTo(jo, pod); - return jo; - } - static T doLoad(const QJsonObject& jo) - { - T pod; - JsonObjectConverter<T>::fillFrom(jo, pod); - return pod; - } - static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } - static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } - }; - - template <typename T> - inline auto toJson(const T& pod) + QJsonObject jo; + JsonObjectConverter<T>::dumpTo(jo, pod); + return jo; + } + static T doLoad(const QJsonObject& jo) { - return JsonConverter<T>::dump(pod); + T pod; + JsonObjectConverter<T>::fillFrom(jo, pod); + return pod; } + static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } + static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } +}; - inline auto toJson(const QJsonObject& jo) { return jo; } +template <typename T> +inline auto toJson(const T& pod) +{ + return JsonConverter<T>::dump(pod); +} - template <typename T> - inline void fillJson(QJsonObject& json, const T& data) - { - JsonObjectConverter<T>::dumpTo(json, data); - } +inline auto toJson(const QJsonObject& jo) { return jo; } - template <typename T> - inline T fromJson(const QJsonValue& jv) - { - return JsonConverter<T>::load(jv); - } +template <typename T> +inline void fillJson(QJsonObject& json, const T& data) +{ + JsonObjectConverter<T>::dumpTo(json, data); +} - template <typename T> - inline T fromJson(const QJsonDocument& jd) - { - return JsonConverter<T>::load(jd); - } +template <typename T> +inline T fromJson(const QJsonValue& jv) +{ + return JsonConverter<T>::load(jv); +} - // Convenience fromJson() overloads that deduce T instead of requiring - // the coder to explicitly type it. They still enforce the - // overwrite-everything semantics of fromJson(), unlike fillFromJson() +template <typename T> +inline T fromJson(const QJsonDocument& jd) +{ + return JsonConverter<T>::load(jd); +} + +// Convenience fromJson() overloads that deduce T instead of requiring +// the coder to explicitly type it. They still enforce the +// overwrite-everything semantics of fromJson(), unlike fillFromJson() + +template <typename T> +inline void fromJson(const QJsonValue& jv, T& pod) +{ + pod = jv.isUndefined() ? T() : fromJson<T>(jv); +} + +template <typename T> +inline void fromJson(const QJsonDocument& jd, T& pod) +{ + pod = fromJson<T>(jd); +} - template <typename T> - inline void fromJson(const QJsonValue& jv, T& pod) +template <typename T> +inline void fillFromJson(const QJsonValue& jv, T& pod) +{ + if (jv.isObject()) + JsonObjectConverter<T>::fillFrom(jv.toObject(), pod); + else if (!jv.isUndefined()) + pod = fromJson<T>(jv); +} + +// JsonConverter<> specialisations + +template <typename T> +struct TrivialJsonDumper { + // Works for: QJsonValue (and all things it can consume), + // QJsonObject, QJsonArray + static auto dump(const T& val) { return val; } +}; + +template <> +struct JsonConverter<bool> : public TrivialJsonDumper<bool> { + static auto load(const QJsonValue& jv) { return jv.toBool(); } +}; + +template <> +struct JsonConverter<int> : public TrivialJsonDumper<int> { + static auto load(const QJsonValue& jv) { return jv.toInt(); } +}; + +template <> +struct JsonConverter<double> : public TrivialJsonDumper<double> { + static auto load(const QJsonValue& jv) { return jv.toDouble(); } +}; + +template <> +struct JsonConverter<float> : public TrivialJsonDumper<float> { + static auto load(const QJsonValue& jv) { return float(jv.toDouble()); } +}; + +template <> +struct JsonConverter<qint64> : public TrivialJsonDumper<qint64> { + static auto load(const QJsonValue& jv) { return qint64(jv.toDouble()); } +}; + +template <> +struct JsonConverter<QString> : public TrivialJsonDumper<QString> { + static auto load(const QJsonValue& jv) { return jv.toString(); } +}; + +template <> +struct JsonConverter<QDateTime> { + static auto dump(const QDateTime& val) = delete; // not provided yet + static auto load(const QJsonValue& jv) { - pod = jv.isUndefined() ? T() : fromJson<T>(jv); + return QDateTime::fromMSecsSinceEpoch(fromJson<qint64>(jv), Qt::UTC); } +}; - template <typename T> - inline void fromJson(const QJsonDocument& jd, T& pod) +template <> +struct JsonConverter<QDate> { + static auto dump(const QDate& val) = delete; // not provided yet + static auto load(const QJsonValue& jv) { - pod = fromJson<T>(jd); + return fromJson<QDateTime>(jv).date(); } +}; + +template <> +struct JsonConverter<QJsonArray> : public TrivialJsonDumper<QJsonArray> { + static auto load(const QJsonValue& jv) { return jv.toArray(); } +}; - template <typename T> - inline void fillFromJson(const QJsonValue& jv, T& pod) +template <> +struct JsonConverter<QByteArray> { + static QString dump(const QByteArray& ba) { return ba.constData(); } + static auto load(const QJsonValue& jv) { - if (jv.isObject()) - JsonObjectConverter<T>::fillFrom(jv.toObject(), pod); - else if (!jv.isUndefined()) - pod = fromJson<T>(jv); + return fromJson<QString>(jv).toLatin1(); } +}; - // JsonConverter<> specialisations +template <> +struct JsonConverter<QVariant> { + static QJsonValue dump(const QVariant& v); + static QVariant load(const QJsonValue& jv); +}; - template <typename T> - struct TrivialJsonDumper +template <typename T> +struct JsonConverter<Omittable<T>> { + static QJsonValue dump(const Omittable<T>& from) { - // Works for: QJsonValue (and all things it can consume), - // QJsonObject, QJsonArray - static auto dump(const T& val) { return val; } - }; - - template <> struct JsonConverter<bool> : public TrivialJsonDumper<bool> + return from.omitted() ? QJsonValue() : toJson(from.value()); + } + static Omittable<T> load(const QJsonValue& jv) { - static auto load(const QJsonValue& jv) { return jv.toBool(); } - }; + if (jv.isUndefined()) + return none; + return fromJson<T>(jv); + } +}; - template <> struct JsonConverter<int> : public TrivialJsonDumper<int> +template <typename VectorT, typename T = typename VectorT::value_type> +struct JsonArrayConverter { + static void dumpTo(QJsonArray& ar, const VectorT& vals) { - static auto load(const QJsonValue& jv) { return jv.toInt(); } - }; - - template <> struct JsonConverter<double> - : public TrivialJsonDumper<double> + for (const auto& v : vals) + ar.push_back(toJson(v)); + } + static auto dump(const VectorT& vals) { - static auto load(const QJsonValue& jv) { return jv.toDouble(); } - }; - - template <> struct JsonConverter<float> : public TrivialJsonDumper<float> + QJsonArray ja; + dumpTo(ja, vals); + return ja; + } + static auto load(const QJsonArray& ja) { - static auto load(const QJsonValue& jv) { return float(jv.toDouble()); } - }; + VectorT vect; + vect.reserve(typename VectorT::size_type(ja.size())); + for (const auto& i : ja) + vect.push_back(fromJson<T>(i)); + return vect; + } + static auto load(const QJsonValue& jv) { return load(jv.toArray()); } + static auto load(const QJsonDocument& jd) { return load(jd.array()); } +}; - template <> struct JsonConverter<qint64> - : public TrivialJsonDumper<qint64> - { - static auto load(const QJsonValue& jv) { return qint64(jv.toDouble()); } - }; +template <typename T> +struct JsonConverter<std::vector<T>> + : public JsonArrayConverter<std::vector<T>> {}; - template <> struct JsonConverter<QString> - : public TrivialJsonDumper<QString> - { - static auto load(const QJsonValue& jv) { return jv.toString(); } - }; +template <typename T> +struct JsonConverter<QVector<T>> : public JsonArrayConverter<QVector<T>> {}; - template <> struct JsonConverter<QDateTime> - { - static auto dump(const QDateTime& val) = delete; // not provided yet - static auto load(const QJsonValue& jv) - { - return QDateTime::fromMSecsSinceEpoch( - fromJson<qint64>(jv), Qt::UTC); - } - }; +template <typename T> +struct JsonConverter<QList<T>> : public JsonArrayConverter<QList<T>> {}; - template <> struct JsonConverter<QDate> +template <> +struct JsonConverter<QStringList> : public JsonConverter<QList<QString>> { + static auto dump(const QStringList& sl) { - static auto dump(const QDate& val) = delete; // not provided yet - static auto load(const QJsonValue& jv) - { - return fromJson<QDateTime>(jv).date(); - } - }; + return QJsonArray::fromStringList(sl); + } +}; - template <> struct JsonConverter<QJsonArray> - : public TrivialJsonDumper<QJsonArray> +template <> +struct JsonObjectConverter<QSet<QString>> { + static void dumpTo(QJsonObject& json, const QSet<QString>& s) { - static auto load(const QJsonValue& jv) { return jv.toArray(); } - }; - - template <> struct JsonConverter<QByteArray> + for (const auto& e : s) + json.insert(toJson(e), QJsonObject {}); + } + static auto fillFrom(const QJsonObject& json, QSet<QString>& s) { - static QString dump(const QByteArray& ba) { return ba.constData(); } - static auto load(const QJsonValue& jv) - { - return fromJson<QString>(jv).toLatin1(); - } - }; + s.reserve(s.size() + json.size()); + for (auto it = json.begin(); it != json.end(); ++it) + s.insert(it.key()); + return s; + } +}; - template <> struct JsonConverter<QVariant> +template <typename HashMapT> +struct HashMapFromJson { + static void dumpTo(QJsonObject& json, const HashMapT& hashMap) { - static QJsonValue dump(const QVariant& v); - static QVariant load(const QJsonValue& jv); - }; - - template <typename T> - struct JsonConverter<Omittable<T>> + for (auto it = hashMap.begin(); it != hashMap.end(); ++it) + json.insert(it.key(), toJson(it.value())); + } + static void fillFrom(const QJsonObject& jo, HashMapT& h) { - static QJsonValue dump(const Omittable<T>& from) - { - return from.omitted() ? QJsonValue() : toJson(from.value()); - } - static Omittable<T> load(const QJsonValue& jv) - { - if (jv.isUndefined()) - return none; - return fromJson<T>(jv); - } - }; + h.reserve(jo.size()); + for (auto it = jo.begin(); it != jo.end(); ++it) + h[it.key()] = fromJson<typename HashMapT::mapped_type>(it.value()); + } +}; - template <typename VectorT, - typename T = typename VectorT::value_type> - struct JsonArrayConverter - { - static void dumpTo(QJsonArray& ar, const VectorT& vals) - { - for (const auto& v: vals) - ar.push_back(toJson(v)); - } - static auto dump(const VectorT& vals) - { - QJsonArray ja; - dumpTo(ja, vals); - return ja; - } - static auto load(const QJsonArray& ja) - { - VectorT vect; vect.reserve(typename VectorT::size_type(ja.size())); - for (const auto& i: ja) - vect.push_back(fromJson<T>(i)); - return vect; - } - static auto load(const QJsonValue& jv) { return load(jv.toArray()); } - static auto load(const QJsonDocument& jd) { return load(jd.array()); } - }; +template <typename T, typename HashT> +struct JsonObjectConverter<std::unordered_map<QString, T, HashT>> + : public HashMapFromJson<std::unordered_map<QString, T, HashT>> {}; - template <typename T> struct JsonConverter<std::vector<T>> - : public JsonArrayConverter<std::vector<T>> - { }; +template <typename T> +struct JsonObjectConverter<QHash<QString, T>> + : public HashMapFromJson<QHash<QString, T>> {}; - template <typename T> struct JsonConverter<QVector<T>> - : public JsonArrayConverter<QVector<T>> - { }; +// We could use std::conditional<> below but QT_VERSION* macros in C++ code +// cause (kinda valid but useless and noisy) compiler warnings about +// bitwise operations on signed integers; so use the preprocessor for now. +using variant_map_t = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + QVariantHash; +#else + QVariantMap; +#endif +template <> +struct JsonConverter<variant_map_t> { + static QJsonObject dump(const variant_map_t& vh); + static QVariantHash load(const QJsonValue& jv); +}; - template <typename T> struct JsonConverter<QList<T>> - : public JsonArrayConverter<QList<T>> - { }; +// Conditional insertion into a QJsonObject - template <> struct JsonConverter<QStringList> - : public JsonConverter<QList<QString>> +namespace _impl { + template <typename ValT> + inline void addTo(QJsonObject& o, const QString& k, ValT&& v) { - static auto dump(const QStringList& sl) - { - return QJsonArray::fromStringList(sl); - } - }; + o.insert(k, toJson(v)); + } - template <> struct JsonObjectConverter<QSet<QString>> + template <typename ValT> + inline void addTo(QUrlQuery& q, const QString& k, ValT&& v) { - static void dumpTo(QJsonObject& json, const QSet<QString>& s) - { - for (const auto& e: s) - json.insert(toJson(e), QJsonObject{}); - } - static auto fillFrom(const QJsonObject& json, QSet<QString>& s) - { - s.reserve(s.size() + json.size()); - for (auto it = json.begin(); it != json.end(); ++it) - s.insert(it.key()); - return s; - } - }; + q.addQueryItem(k, QStringLiteral("%1").arg(v)); + } - template <typename HashMapT> - struct HashMapFromJson + // OpenAPI is entirely JSON-based, which means representing bools as + // textual true/false, rather than 1/0. + inline void addTo(QUrlQuery& q, const QString& k, bool v) { - static void dumpTo(QJsonObject& json, const HashMapT& hashMap) - { - for (auto it = hashMap.begin(); it != hashMap.end(); ++it) - json.insert(it.key(), toJson(it.value())); - } - static void fillFrom(const QJsonObject& jo, HashMapT& h) - { - h.reserve(jo.size()); - for (auto it = jo.begin(); it != jo.end(); ++it) - h[it.key()] = - fromJson<typename HashMapT::mapped_type>(it.value()); - } - }; - - template <typename T> - struct JsonObjectConverter<std::unordered_map<QString, T>> - : public HashMapFromJson<std::unordered_map<QString, T>> - { }; - - template <typename T> - struct JsonObjectConverter<QHash<QString, T>> - : public HashMapFromJson<QHash<QString, T>> - { }; + q.addQueryItem(k, v ? QStringLiteral("true") : QStringLiteral("false")); + } - // We could use std::conditional<> below but QT_VERSION* macros in C++ code - // cause (kinda valid but useless and noisy) compiler warnings about - // bitwise operations on signed integers; so use the preprocessor for now. - using variant_map_t = -#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) - QVariantHash; -#else - QVariantMap; -#endif - template <> struct JsonConverter<variant_map_t> + inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals) { - static QJsonObject dump(const variant_map_t& vh); - static QVariantHash load(const QJsonValue& jv); - }; - - // Conditional insertion into a QJsonObject + for (const auto& v : vals) + q.addQueryItem(k, v); + } - namespace _impl + inline void addTo(QUrlQuery& q, const QString&, const QJsonObject& vals) { - template <typename ValT> - inline void addTo(QJsonObject& o, const QString& k, ValT&& v) - { o.insert(k, toJson(v)); } - - template <typename ValT> - inline void addTo(QUrlQuery& q, const QString& k, ValT&& v) - { q.addQueryItem(k, QStringLiteral("%1").arg(v)); } + for (auto it = vals.begin(); it != vals.end(); ++it) + q.addQueryItem(it.key(), it.value().toString()); + } - // OpenAPI is entirely JSON-based, which means representing bools as - // textual true/false, rather than 1/0. - inline void addTo(QUrlQuery& q, const QString& k, bool v) + // This one is for types that don't have isEmpty() and for all types + // when Force is true + template <typename ValT, bool Force = true, typename = bool> + struct AddNode { + template <typename ContT, typename ForwardedT> + static void impl(ContT& container, const QString& key, + ForwardedT&& value) { - q.addQueryItem(k, v ? QStringLiteral("true") - : QStringLiteral("false")); + addTo(container, key, std::forward<ForwardedT>(value)); } + }; - inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals) + // This one is for types that have isEmpty() when Force is false + template <typename ValT> + struct AddNode<ValT, false, decltype(std::declval<ValT>().isEmpty())> { + template <typename ContT, typename ForwardedT> + static void impl(ContT& container, const QString& key, + ForwardedT&& value) { - for (const auto& v: vals) - q.addQueryItem(k, v); + if (!value.isEmpty()) + addTo(container, key, std::forward<ForwardedT>(value)); } + }; - inline void addTo(QUrlQuery& q, const QString&, const QJsonObject& vals) + // This one unfolds Omittable<> (also only when Force is false) + template <typename ValT> + struct AddNode<Omittable<ValT>, false> { + template <typename ContT, typename OmittableT> + static void impl(ContT& container, const QString& key, + const OmittableT& value) { - for (auto it = vals.begin(); it != vals.end(); ++it) - q.addQueryItem(it.key(), it.value().toString()); + if (!value.omitted()) + addTo(container, key, value.value()); } - - // This one is for types that don't have isEmpty() and for all types - // when Force is true - template <typename ValT, bool Force = true, typename = bool> - struct AddNode - { - template <typename ContT, typename ForwardedT> - static void impl(ContT& container, const QString& key, - ForwardedT&& value) - { - addTo(container, key, std::forward<ForwardedT>(value)); - } - }; - - // This one is for types that have isEmpty() when Force is false - template <typename ValT> - struct AddNode<ValT, false, - decltype(std::declval<ValT>().isEmpty())> - { - template <typename ContT, typename ForwardedT> - static void impl(ContT& container, const QString& key, - ForwardedT&& value) - { - if (!value.isEmpty()) - addTo(container, key, std::forward<ForwardedT>(value)); - } - }; - - // This one unfolds Omittable<> (also only when Force is false) - template <typename ValT> - struct AddNode<Omittable<ValT>, false> - { - template <typename ContT, typename OmittableT> - static void impl(ContT& container, - const QString& key, const OmittableT& value) - { - if (!value.omitted()) - addTo(container, key, value.value()); - } - }; + }; #if 0 // This is a special one that unfolds optional<> @@ -426,37 +400,37 @@ namespace QMatrixClient }; #endif - } // namespace _impl - - static constexpr bool IfNotEmpty = false; - - /*! Add a key-value pair to QJsonObject or QUrlQuery - * - * Adds a key-value pair(s) specified by \p key and \p value to - * \p container, optionally (in case IfNotEmpty is passed for the first - * template parameter) taking into account the value "emptiness". - * With IfNotEmpty, \p value is NOT added to the container if and only if: - * - it has a method `isEmpty()` and `value.isEmpty() == true`, or - * - it's an `Omittable<>` and `value.omitted() == true`. - * - * If \p container is a QUrlQuery, an attempt to fit \p value into it is - * made as follows: - * - if \p value is a QJsonObject, \p key is ignored and pairs from \p value - * are copied to \p container, assuming that the value in each pair - * is a string; - * - if \p value is a QStringList, it is "exploded" into a list of key-value - * pairs with key equal to \p key and value taken from each list item; - * - if \p value is a bool, its OpenAPI (i.e. JSON) representation is added - * to the query (`true` or `false`, respectively). - * - * \tparam Force add the pair even if the value is empty. This is true - * by default; passing IfNotEmpty or false for this parameter - * enables emptiness checks as described above - */ - template <bool Force = true, typename ContT, typename ValT> - inline void addParam(ContT& container, const QString& key, ValT&& value) - { - _impl::AddNode<std::decay_t<ValT>, Force> - ::impl(container, key, std::forward<ValT>(value)); - } -} // namespace QMatrixClient +} // namespace _impl + +static constexpr bool IfNotEmpty = false; + +/*! Add a key-value pair to QJsonObject or QUrlQuery + * + * Adds a key-value pair(s) specified by \p key and \p value to + * \p container, optionally (in case IfNotEmpty is passed for the first + * template parameter) taking into account the value "emptiness". + * With IfNotEmpty, \p value is NOT added to the container if and only if: + * - it has a method `isEmpty()` and `value.isEmpty() == true`, or + * - it's an `Omittable<>` and `value.omitted() == true`. + * + * If \p container is a QUrlQuery, an attempt to fit \p value into it is + * made as follows: + * - if \p value is a QJsonObject, \p key is ignored and pairs from \p value + * are copied to \p container, assuming that the value in each pair + * is a string; + * - if \p value is a QStringList, it is "exploded" into a list of key-value + * pairs with key equal to \p key and value taken from each list item; + * - if \p value is a bool, its OpenAPI (i.e. JSON) representation is added + * to the query (`true` or `false`, respectively). + * + * \tparam Force add the pair even if the value is empty. This is true + * by default; passing IfNotEmpty or false for this parameter + * enables emptiness checks as described above + */ +template <bool Force = true, typename ContT, typename ValT> +inline void addParam(ContT& container, const QString& key, ValT&& value) +{ + _impl::AddNode<std::decay_t<ValT>, Force>::impl(container, key, + std::forward<ValT>(value)); +} +} // namespace Quotient |