diff --git a/src/blackmisc/compare.h b/src/blackmisc/compare.h index 32584510b..24c18cf0c 100644 --- a/src/blackmisc/compare.h +++ b/src/blackmisc/compare.h @@ -13,10 +13,63 @@ #define BLACKMISC_COMPARE_H #include "blackmisc/tuple.h" +#include "blackmisc/metaclass.h" #include "blackmisc/inheritancetraits.h" +#include "blackmisc/typetraits.h" namespace BlackMisc { + //! \cond PRIVATE + namespace Private + { + template + int compareImpl(const T &a, const U &b, std::true_type) + { + return compare(a, b); + } + + template + int compareImpl(const T &a, const U &b, std::false_type) + { + return a < b ? -1 : b < a ? 1 : 0; + } + + template + struct CompareHelper + { + template + static int compareTuples(const T &a, const U &b) + { + int cmp = compareImpl(std::get(a), std::get(b), HasCompare, std::tuple_element_t>()); + return cmp ? cmp : CompareHelper::compareTuples(a, b); + } + }; + + template + struct CompareHelper + { + template + static int compareTuples(const T &, const U &) + { + return 0; + } + }; + } + //! \endcond + + /*! + * Lexicographically compare two tuples and return negative, positive, or zero, + * if a is less than, greater than, or equal to b. + * Each element is compared with compare(a', b') if supported by the element type, operator less-than otherwise. + * \todo Rename to compare() after removing tuple.h, which contains a function with the same name and signature. + */ + template + int compareTuples(const std::tuple &a, const std::tuple &b) + { + static_assert(sizeof...(Ts) == sizeof...(Us), "tuples must be same size"); + return Private::CompareHelper::compareTuples(a, b); + } + namespace Mixin { @@ -59,6 +112,30 @@ namespace BlackMisc static bool baseEquals(const CEmpty *, const CEmpty *) { return true; } }; + /*! + * CRTP class template from which a derived class can inherit operator== implemented by metaclass. + */ + template + class EqualsByMetaClass + { + public: + //! Equals + friend bool operator ==(const Derived &a, const Derived &b) { return equals(a, b); } + + //! Not equal + friend bool operator !=(const Derived &a, const Derived &b) { return ! equals(a, b); } + + private: + static bool equals(const Derived &a, const Derived &b) + { + auto meta = introspect().without(MetaFlags()); + return meta.toCaseAwareTuple(a) == meta.toCaseAwareTuple(b) && baseEquals(static_cast *>(&a), static_cast *>(&b)); + } + template static bool baseEquals(const T *a, const T *b) { return *a == *b; } + static bool baseEquals(const void *, const void *) { return true; } + static bool baseEquals(const CEmpty *, const CEmpty *) { return true; } + }; + /*! * CRTP class template from which a derived class can inherit operator< implemented using its compare function. * @@ -111,6 +188,37 @@ namespace BlackMisc static bool baseLess(const CEmpty *, const CEmpty *) { return false; } }; + /*! + * CRTP class template from which a derived class can inherit operator< implemented by metaclass. + */ + template + class LessThanByMetaClass + { + public: + //! Less than + friend bool operator <(const Derived &a, const Derived &b) { return less(a, b); } + + //! Greater than + friend bool operator >(const Derived &a, const Derived &b) { return less(b, a); } + + //! Less than or equal + friend bool operator <=(const Derived &a, const Derived &b) { return ! less(b, a); } + + //! Greater than or equal + friend bool operator >=(const Derived &a, const Derived &b) { return ! less(a, b); } + + private: + static bool less(const Derived &a, const Derived &b) + { + if (baseLess(static_cast *>(&a), static_cast *>(&b))) { return true; } + auto meta = introspect().without(MetaFlags()); + return meta.toCaseAwareTuple(a) < meta.toCaseAwareTuple(b); + } + template static bool baseLess(const T *a, const T *b) { return *a < *b; } + static bool baseLess(const void *, const void *) { return false; } + static bool baseLess(const CEmpty *, const CEmpty *) { return false; } + }; + /*! * CRTP class template from which a derived class can inherit non-member compare() implemented by metatuple. * @@ -135,6 +243,29 @@ namespace BlackMisc static int baseCompare(const CEmpty *, const CEmpty *) { return 0; } }; + /*! + * CRTP class template from which a derived class can inherit non-member compare() implemented by metaclass. + */ + template + class CompareByMetaClass + { + public: + //! Return negative, zero, or positive if a is less than, equal to, or greater than b. + friend int compare(const Derived &a, const Derived &b) { return compareImpl(a, b); } + + private: + static int compareImpl(const Derived &a, const Derived &b) + { + int baseCmp = baseCompare(static_cast *>(&a), static_cast *>(&b)); + if (baseCmp) { return baseCmp; } + auto meta = introspect().without(MetaFlags()); + return BlackMisc::compareTuples(meta.toCaseAwareTuple(a), meta.toCaseAwareTuple(b)); + } + template static int baseCompare(const T *a, const T *b) { return compare(*a, *b); } + static int baseCompare(const void *, const void *) { return 0; } + static int baseCompare(const CEmpty *, const CEmpty *) { return 0; } + }; + } // Mixin } // BlackMisc diff --git a/src/blackmisc/dbus.h b/src/blackmisc/dbus.h index f8cb9650c..b0d0464b1 100644 --- a/src/blackmisc/dbus.h +++ b/src/blackmisc/dbus.h @@ -13,6 +13,7 @@ #define BLACKMISC_DBUS_H #include "blackmisc/tuple.h" +#include "blackmisc/metaclass.h" #include "blackmisc/inheritancetraits.h" #include #include @@ -88,6 +89,43 @@ namespace BlackMisc static void baseUnmarshall(CEmpty *, const QDBusArgument &) {} }; + /*! + * CRTP class template from which a derived class can inherit common methods dealing with marshalling instances by metaclass. + * + * \see BLACKMISC_DECLARE_USING_MIXIN_DBUS + */ + template + class DBusByMetaClass : public DBusOperators + { + public: + //! Marshall without begin/endStructure, for when composed within another object + void marshallToDbus(QDBusArgument &arg) const + { + baseMarshall(static_cast *>(derived()), arg); + auto meta = introspect().without(MetaFlags()); + meta.forEachMember(*derived(), [ & ](const auto &member) { arg << member; }); + } + + //! Unmarshall without begin/endStructure, for when composed within another object + void unmarshallFromDbus(const QDBusArgument &arg) + { + baseUnmarshall(static_cast *>(derived()), arg); + auto meta = introspect().without(MetaFlags()); + meta.forEachMember(*derived(), [ & ](auto &member) { arg >> member; }); + } + + private: + const Derived *derived() const { return static_cast(this); } + Derived *derived() { return static_cast(this); } + + template static void baseMarshall(const T *base, QDBusArgument &arg) { base->marshallToDbus(arg); } + template static void baseUnmarshall(T *base, const QDBusArgument &arg) { base->unmarshallFromDbus(arg); } + static void baseMarshall(const void *, QDBusArgument &) {} + static void baseUnmarshall(void *, const QDBusArgument &) {} + static void baseMarshall(const CEmpty *, QDBusArgument &) {} + static void baseUnmarshall(CEmpty *, const QDBusArgument &) {} + }; + /*! * When a derived class and a base class both inherit from Mixin::DBusByTuple, * the derived class uses this macro to disambiguate the inherited members. diff --git a/src/blackmisc/dictionary.h b/src/blackmisc/dictionary.h index 695d029fe..c489c0a69 100644 --- a/src/blackmisc/dictionary.h +++ b/src/blackmisc/dictionary.h @@ -19,6 +19,7 @@ #include "range.h" #include "containerbase.h" #include "typetraits.h" +#include "metaclass.h" #include #include #include @@ -47,6 +48,14 @@ namespace BlackMisc struct DefaultType { static_assert(std::is_void::value, "Key does not support either QHash or QMap"); }; }; + // Work around MSVC2015 bug affecting generic lambda + struct Hasher + { + template + void operator()(const T &object) { m_hash ^= qHash(object); } + uint &m_hash; + }; + //! \endcond } // namespace Private @@ -475,6 +484,35 @@ namespace BlackMisc static uint baseHash(const void *) { return 0; } static uint baseHash(const CEmpty *) { return 0; } }; + + /*! + * CRTP class template from which a derived class can inherit common methods dealing with hashing instances by metaclass. + * + * \tparam Derived Must be registered with BLACK_DECLARE_TUPLE_CONVERSION. + */ + template + class HashByMetaClass + { + public: + //! qHash overload, needed for storing value in a QSet. + friend uint qHash(const Derived &value, uint seed = 0) + { + return ::qHash(hashImpl(value), seed); + } + + private: + static uint hashImpl(const Derived &value) + { + uint hash = baseHash(static_cast *>(&value)); + auto meta = introspect().without(MetaFlags()); + meta.forEachMember(value, Private::Hasher { hash }); + return hash; + } + + template static uint baseHash(const T *base) { return qHash(*base); } + static uint baseHash(const void *) { return 0; } + static uint baseHash(const CEmpty *) { return 0; } + }; } /*! diff --git a/src/blackmisc/json.h b/src/blackmisc/json.h index 8992a6fa8..007fd3a9d 100644 --- a/src/blackmisc/json.h +++ b/src/blackmisc/json.h @@ -14,6 +14,7 @@ #include "blackmisc/blackmiscexport.h" #include "blackmisc/tuple.h" +#include "blackmisc/metaclass.h" #include "blackmisc/inheritancetraits.h" #include "blackmisc/fileutils.h" #include @@ -286,6 +287,60 @@ namespace BlackMisc static void baseConvertFromJson(CEmpty *, const QJsonObject &) {} }; + /*! + * CRTP class template from which a derived class can inherit common methods dealing with JSON by metatuple. + * + * \see BLACKMISC_DECLARE_USING_MIXIN_JSON + */ + template + class JsonByMetaClass : public JsonOperators + { + public: + //! Cast to JSON object + QJsonObject toJson() const + { + QJsonObject json; + auto meta = introspect().without(MetaFlags()); + meta.forEachMemberName(*derived(), [ & ](const auto &member, const QString &name) + { + json << std::pair &>(name, member); // std::make_pair causes an ambiguous operator<< + }); + return Json::appendJsonObject(json, baseToJson(static_cast *>(derived()))); + } + + //! Convenience function JSON as string + QString toJsonString(QJsonDocument::JsonFormat format = QJsonDocument::Indented) const + { + QJsonDocument jsonDoc(toJson()); + return jsonDoc.toJson(format); + } + + //! Assign from JSON object + void convertFromJson(const QJsonObject &json) + { + baseConvertFromJson(static_cast *>(derived()), json); + auto meta = introspect().without(MetaFlags()); + meta.forEachMemberName(*derived(), [ & ](auto &member, const QString &name) { json.value(name) >> member; }); + } + + //! Assign from JSON object string + void convertFromJson(const QString &jsonString) + { + convertFromJson(BlackMisc::Json::jsonObjectFromString(jsonString)); + } + + private: + const Derived *derived() const { return static_cast(this); } + Derived *derived() { return static_cast(this); } + + template static QJsonObject baseToJson(const T *base) { return base->toJson(); } + template static void baseConvertFromJson(T *base, const QJsonObject &json) { base->convertFromJson(json); } + static QJsonObject baseToJson(const void *) { return {}; } + static void baseConvertFromJson(void *, const QJsonObject &) {} + static QJsonObject baseToJson(const CEmpty *) { return {}; } + static void baseConvertFromJson(CEmpty *, const QJsonObject &) {} + }; + /*! * When a derived class and a base class both inherit from Mixin::JsonByTuple, * the derived class uses this macro to disambiguate the inherited members. diff --git a/src/blackmisc/typetraits.h b/src/blackmisc/typetraits.h index 3aac5a10f..5ccce43c6 100644 --- a/src/blackmisc/typetraits.h +++ b/src/blackmisc/typetraits.h @@ -76,6 +76,16 @@ namespace BlackMisc struct ModelsQMapKey() < std::declval())>> : public std::true_type {}; //! \endcond + /*! + * Trait which is true if the expression compare(a, b) is valid when a and b are instances of T and U. + */ + template > + struct HasCompare : public std::false_type {}; + //! \cond + template + struct HasCompare(), std::declval()))>> : public std::true_type {}; + //! \endcond + } #endif diff --git a/src/blackmisc/valueobject.h b/src/blackmisc/valueobject.h index cd75c23b4..d29302aba 100644 --- a/src/blackmisc/valueobject.h +++ b/src/blackmisc/valueobject.h @@ -14,6 +14,7 @@ #include "blackmiscexport.h" #include "dbus.h" +#include "metaclass.h" #include "tuple.h" #include "json.h" #include "compare.h"