diff --git a/src/blackmisc/metaclass.h b/src/blackmisc/metaclass.h new file mode 100644 index 000000000..f15778edd --- /dev/null +++ b/src/blackmisc/metaclass.h @@ -0,0 +1,319 @@ +/* Copyright (C) 2016 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_METACLASS_H +#define BLACKMISC_METACLASS_H + +#include "blackmisc/metaclassprivate.h" +#include "blackmisc/invoke.h" +#include "blackmisc/tuple.h" // just for TupleConverterFlags + +/*! + * \defgroup MetaClass Metaclass system + * Compile-time reflection toolkit for iterating over + * members of value classes. + */ + +/*! + * Macro to define a nested metaclass that describes the attributes of its + * enclosing class. Use in the private section of the class. + * + * \tparam CLASS The name of the class containing the member. + * \note A semicolon is needed at the end. + * \ingroup MetaClass + */ +#define BLACK_METACLASS(CLASS, ...) \ + friend struct BlackMisc::Private::CMetaClassAccessor; \ + struct MetaClass : public BlackMisc::CMetaClass \ + { \ + using Class = CLASS; \ + BLACK_NO_EXPORT_CONSTEXPR static auto getMemberList() \ + BLACK_TRAILING_RETURN(CMetaClass::makeMetaMemberList(__VA_ARGS__)) \ + { \ + return CMetaClass::makeMetaMemberList(__VA_ARGS__); \ + } \ + } + +/*! + * Macro to define an element within a metaclass. + * + * Additional arguments can be supplied in the variadic part, which will be + * forwarded to CMetaClass::makeMetaMember. + * + * \tparam MEMBER The name of the member without m_ part. + * \see BLACK_METACLASS + * \see BLACK_METAMEMBER_NAMED + * \see BlackMisc::CMetaClass::makeMetaMember + * \ingroup MetaClass + */ +#define BLACK_METAMEMBER(MEMBER, ...) \ + CMetaClass::makeMetaMember( \ + &Class::m_##MEMBER, #MEMBER BLACK_TRAILING_VA_ARGS(__VA_ARGS__) \ + ) + +/*! + * Same as BLACK_METAMEMBER but the second parameter is a string literal + * containing the JSON name of the member. + * + * \ingroup MetaClass + */ +#define BLACK_METAMEMBER_NAMED(MEMBER, NAME, ...) \ + CMetaClass::makeMetaMember( \ + &Class::m_##MEMBER, NAME BLACK_TRAILING_VA_ARGS(__VA_ARGS__) \ + ) + +namespace BlackMisc +{ + + class CVariant; + + /*! + * Metadata flags attached to members of a meta class. + * \todo Remove TupleConverterFlags and change MetaFlag to a full enum. + * \ingroup MetaClass + */ + using MetaFlag = TupleConverterFlags; + //enum MetaFlag + //{ + // DisabledForComparison = 1 << 0, //!< Element will be ignored by compare() and comparison operators + // DisabledForMarshalling = 1 << 1, //!< Element will be ignored during DBus marshalling + // DisabledForDebugging = 1 << 2, //!< Element will be ignored when streaming to QDebug + // DisabledForHashing = 1 << 3, //!< Element will be ignored by qHash() + // DisabledForJson = 1 << 4, //!< Element will be ignored during JSON serialization + // CaseInsensitiveComparison = 1 << 5 //!< Element will be compared case insensitively (must be a QString) + //}; + + /*! + * Type wrapper for passing MetaFlag to CMetaClassIntrospector::with and CMetaClassIntrospector::without. + * \ingroup MetaClass + */ + template + using MetaFlags = std::integral_constant; + + /*! + * Literal aggregate type representing attributes of one member of a value class. + * \ingroup MetaClass + */ + template + struct CMetaMember + { + //! Pointer to the member. + const M m_ptr; + + //! Member name. + const char *const m_name; + + //! Property index of the member. + //! \todo Not used yet. + const int m_index; + + //! Any flags applying to the member. + const quint64 m_flags; + + //! True if m_flags contains Flags. + template + constexpr bool has(Flags) const { return (m_flags & Flags::value) == Flags::value; } + + //! Invoke the member on an instance of the value class. + template + decltype(auto) in(T &&object, Ts &&... args) const + { + return Private::invoke(m_ptr, std::forward(object), std::forward(args)...); + } + }; + + /*! + * Literal aggregate type representing attributes of the members of a value class. + * \ingroup MetaClass + */ + template + struct CMetaMemberList + { + //! Tuple of CMetaMember. + const Private::tuple m_members; + + //! Number of members. + static constexpr size_t c_size = sizeof...(Members); + + //! Convenience method returning the member at index I. + template + constexpr auto at(std::integral_constant = {}) const BLACK_TRAILING_RETURN(Private::get(m_members)) + { + return Private::get(m_members); + } + }; + + /*! + * Base class for meta classes. + * Just static protected members to be used by derived meta classes. + * \ingroup MetaClass + */ + class CMetaClass + { + protected: + //! Return a CMetaMemberList of type deduced from the types of the meta members. + //! Usually not used directly, but via the macros. + template + constexpr static CMetaMemberList makeMetaMemberList(Members... members) + { + return { { members... } }; + } + + //! Return a CMetaMethod of type deduced from the type of the member. + //! Usually not used directly, but via the macros. + template + constexpr static CMetaMember makeMetaMember(M ptrToMember, const char *name = nullptr, int index = 0, quint64 flags = 0) + { + static_assert(std::is_member_object_pointer::value, "M must be a pointer to member object"); + return { ptrToMember, name, index, flags }; + } + }; + + /*! + * Implementation of an introspector for the metaclass of T. + * Obtain an instance of this class via BlackMisc::introspect. + * \ingroup MetaClass + */ + template + class CMetaClassIntrospector + { + public: + //! Return a CMetaClassIntrospector covering only those members which have the given flags. + //! \see BlackMisc::MetaFlags + template + static auto with(Flags) { return filter(MaskSequence<(members().at(index()).has(Flags()))...>()); } + + //! Return a CMetaClassIntrospector covering only those members which do not have the given flags. + //! \see BlackMisc::MetaFlags + template + static auto without(Flags) { return filter(MaskSequence<(! members().at(index()).has(Flags()))...>()); } + + //! Return a tuple containing references to all members of object. + //! @{ + static auto toTuple(T &object) { return std::tie((members().at(index()).in(object))...); } + static auto toTuple(const T &object) { return std::tie((members().at(index()).in(object))...); } + //! @} + + //! Like toTuple, but members with the CaseInsensitiveComparison flag will be wrapped so that their comparisons are case insensitive. + static auto toCaseAwareTuple(const T &object) { return std::make_tuple(caseAwareWrap(members().at(index()).in(object))...); } + + //! For each member in object, pass member as argument to visitor function. + //! @{ + template + static void forEachMember(T &object, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(object)); }); + } + template + static void forEachMember(const T &object, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(object)); }); + } + //! @} + + //! For each member in object pair, pass member pair as arguments to visitor function. + //! @{ + template + static void forEachMemberPair(T &left, T &right, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(left), member.in(right)); }); + } + template + static void forEachMemberPair(const T &left, T &right, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(left), member.in(right)); }); + } + template + static void forEachMemberPair(T &left, const T &right, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(left), member.in(right)); }); + } + template + static void forEachMemberPair(const T &left, const T &right, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(left), member.in(right)); }); + } + //! @} + + //! For each member in object, pass member and its name as arguments to visitor function. + //! @{ + template + static void forEachMemberName(T &object, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(object), QString(member.m_name)); }); + } + template + static void forEachMemberName(const T &object, F &&visitor) + { + forEachImpl([ & ](auto &&member) { std::forward(visitor)(member.in(object), QString(member.m_name)); }); + } + //! @} + + private: + template + using MaskSequence = Private::MaskSequence, Mask...>; + + template + static auto filter(Private::index_sequence) { return CMetaClassIntrospector(); } + + template + using index = std::integral_constant; + + constexpr static auto members() BLACK_TRAILING_RETURN(MetaClass::getMemberList()) { return MetaClass::getMemberList(); } + + template + static void forEachImpl(F &&visitor) + { + // parameter pack swallow idiom + static_cast(std::initializer_list { (static_cast(std::forward(visitor)(members().at(index()))), 0)... }); + } + + template + static auto caseAwareWrap(const U &value) + { + using IsCaseInsensitive = std::integral_constant()).has(MetaFlags())>; + return Private::caseAwareWrap(IsCaseInsensitive(), value); + } + }; + + namespace Private + { + //! \private Friend class of all value classes, so it can access the private nested class. + struct CMetaClassAccessor + { + template + static auto getIntrospector(index_sequence) + { + return CMetaClassIntrospector(); + } + + template + static auto getIntrospector() + { + return getIntrospector(Private::make_index_sequence()); + } + }; + } + + /*! + * Obtain the CMetaClassIntrospector for the metaclass of T. + * \return BlackMisc::CMetaClassIntrospector + * \ingroup MetaClass + */ + template + auto introspect() + { + return Private::CMetaClassAccessor::getIntrospector(); + } + +} // namespace + +#endif diff --git a/src/blackmisc/metaclassprivate.h b/src/blackmisc/metaclassprivate.h new file mode 100644 index 000000000..4edb581e4 --- /dev/null +++ b/src/blackmisc/metaclassprivate.h @@ -0,0 +1,139 @@ +/* Copyright (C) 2016 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_METACLASSPRIVATE_H +#define BLACKMISC_METACLASSPRIVATE_H + +#include "integersequence.h" +#include +#include +#include + +//! \cond PRIVATE + +// GCC 4.9 doesn't utilize constexpr in the standard library +#if ! (defined(Q_CC_GNU) && __GNUC__ <= 4) +#define BLACK_HAS_CONSTEXPR_STDLIB +#endif + +// Work around MinGW problem with combination of constexpr and extern template +#if defined(Q_OS_WIN) && defined(Q_CC_GNU) +#define BLACK_NO_EXPORT_CONSTEXPR constexpr inline __attribute__((gnu_inline)) +#else +#define BLACK_NO_EXPORT_CONSTEXPR constexpr +#endif + +// MSVC, GCC, Clang all have non-standard extensions for skipping trailing +// commas in variadic macros, but the MSVC extension differs from the others. +#ifdef Q_CC_MSVC +#define BLACK_TRAILING_VA_ARGS(...) ,__VA_ARGS__ +#else +#define BLACK_TRAILING_VA_ARGS(...) ,##__VA_ARGS__ +#endif + +// Work around MSVC constexpr bug +// https://connect.microsoft.com/VisualStudio/feedback/details/2028721 +#ifdef Q_CC_MSVC +#define BLACK_DECLTYPE_AUTO auto +#define BLACK_TRAILING_RETURN(EXPR) -> decltype(EXPR) +#else +#define BLACK_DECLTYPE_AUTO decltype(auto) +#define BLACK_TRAILING_RETURN(EXPR) +#endif + +namespace BlackMisc +{ + namespace Private + { +#ifdef BLACK_HAS_CONSTEXPR_STDLIB + using std::tuple; + using std::tuple_size; + using std::get; + using std::make_tuple; +#else // Own implementation of tuple, because the one in GCC 4.9 is not constexpr. + template + struct conslist; + + template + struct conslist + { + constexpr conslist() {} + constexpr conslist(const Head &v) : m_obj(v) {} + constexpr const Head &get(std::integral_constant) const { return m_obj; } + Head m_obj; + }; + + template + struct conslist : public conslist + { + constexpr conslist() {} + constexpr conslist(const Head &v, const Tail &... vs) : conslist(vs...), m_obj(v) {} + constexpr const Head &get(std::integral_constant) const { return m_obj; } + using conslist::get; + Head m_obj; + }; + + template + struct tuple + { + constexpr tuple() {} + constexpr tuple(const Ts &... vs) : m_conslist(vs...) {} + conslist<0, Ts...> m_conslist; + constexpr static size_t c_size = sizeof...(Ts); + }; + + template + struct tuple_size : public std::integral_constant {}; + + template + constexpr decltype(auto) get(T &&tuple) { return std::forward(tuple).m_conslist.get(std::integral_constant()); } + + template + constexpr auto make_tuple(Ts &&... vs) { return tuple...>(std::forward(vs)...); } +#endif // ! BLACK_HAS_CONSTEXPR_STDLIB + + // Helper for case insensitive comparisons. + template + struct CaseInsensitiveWrapper + { + const T &m_ref; + + explicit CaseInsensitiveWrapper(const T &ref) : m_ref(ref) {} + + friend int compare(CaseInsensitiveWrapper a, CaseInsensitiveWrapper b) + { + return a.m_ref.compare(b.m_ref, Qt::CaseInsensitive); + } + friend bool operator ==(CaseInsensitiveWrapper a, CaseInsensitiveWrapper b) + { + return compare(a, b) == 0; + } + friend bool operator <(CaseInsensitiveWrapper a, CaseInsensitiveWrapper b) + { + return compare(a, b) < 0; + } + }; + template + auto caseAwareWrap(std::false_type, const T &value) + { + return std::cref(value); + } + template + auto caseAwareWrap(std::true_type, const T &value) + { + return CaseInsensitiveWrapper(value); + } + } +} + +//! \endcond + +#endif