diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2017-05-18 03:37:06 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2017-05-22 09:39:56 +0900 |
commit | bb26ca86ad350f2562b51284e7c631b1e4f77106 (patch) | |
tree | 8fe867870d8e8a8488ece979634035109e39e5eb | |
parent | ced7a66686596e74a1f25b5d9634b9b562870943 (diff) | |
download | libquotient-bb26ca86ad350f2562b51284e7c631b1e4f77106.tar.gz libquotient-bb26ca86ad350f2562b51284e7c631b1e4f77106.zip |
util.h: lookup() uses forwarding refs; added Dispatch/dispatch and REGISTER_ENUM
The Dispatch<> template and dispatch(), a facility function for it, simplify dispatching to functions that have different signatures that still can be converted to the same std::function<> type. The case in point is in event.cpp; Event::fromJson calls make() that always returns the type we need; however, once we have several possible base types (Event, RoomEvent, StateEvent), we'd have to either write a specific make() incarnation for each of them, or mess with function return type conversions. Dispatch<> helps to keep the code clean.
REGISTER_ENUM is a cross-Qt versions approach to dumping enumeration values to qDebug() and the likes.
-rw-r--r-- | events/event.cpp | 27 | ||||
-rw-r--r-- | events/roommessageevent.cpp | 20 | ||||
-rw-r--r-- | util.h | 144 |
3 files changed, 154 insertions, 37 deletions
diff --git a/events/event.cpp b/events/event.cpp index 07649b02..5df816fe 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -87,27 +87,26 @@ QString Event::originalJson() const return d->originalJson; } -template <typename T> -Event* make(const QJsonObject& obj) +template <typename EventT> +EventT* make(const QJsonObject& obj) { - return T::fromJson(obj); + return EventT::fromJson(obj); } Event* Event::fromJson(const QJsonObject& obj) { - auto delegate = lookup(obj.value("type").toString(), - "m.room.message", make<RoomMessageEvent>, - "m.room.name", make<RoomNameEvent>, - "m.room.aliases", make<RoomAliasesEvent>, - "m.room.canonical_alias", make<RoomCanonicalAliasEvent>, - "m.room.member", make<RoomMemberEvent>, - "m.room.topic", make<RoomTopicEvent>, - "m.typing", make<TypingEvent>, - "m.receipt", make<ReceiptEvent>, + return dispatch<Event*>(obj).to(obj["type"].toString(), + "m.room.message", &make<RoomMessageEvent>, + "m.room.name", &make<RoomNameEvent>, + "m.room.aliases", &make<RoomAliasesEvent>, + "m.room.canonical_alias", &make<RoomCanonicalAliasEvent>, + "m.room.member", &make<RoomMemberEvent>, + "m.room.topic", &make<RoomTopicEvent>, + "m.typing", &make<TypingEvent>, + "m.receipt", &make<ReceiptEvent>, /* Insert new event types BEFORE this line */ - make<UnknownEvent> + &make<UnknownEvent> ); - return delegate(obj); } bool Event::parseJson(const QJsonObject& obj) diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 677bb79f..d5e960a1 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -120,17 +120,17 @@ RoomMessageEvent* RoomMessageEvent::fromJson(const QJsonObject& obj) { e->d->plainBody = content["body"].toString(); - auto delegate = lookup(content.value("msgtype").toString(), - "m.text", make<MessageEventType::Text, TextContent>, - "m.emote", make<MessageEventType::Emote, TextContent>, - "m.notice", make<MessageEventType::Notice, TextContent>, - "m.image", make<MessageEventType::Image, ImageContent>, - "m.file", make<MessageEventType::File, FileContent>, - "m.location", make<MessageEventType::Location, LocationContent>, - "m.video", makeVideo, - "m.audio", make<MessageEventType::Audio, AudioContent>, + auto delegate = lookup(content["msgtype"].toString(), + "m.text", &make<MessageEventType::Text, TextContent>, + "m.emote", &make<MessageEventType::Emote, TextContent>, + "m.notice", &make<MessageEventType::Notice, TextContent>, + "m.image", &make<MessageEventType::Image, ImageContent>, + "m.file", &make<MessageEventType::File, FileContent>, + "m.location", &make<MessageEventType::Location, LocationContent>, + "m.video", &makeVideo, + "m.audio", &make<MessageEventType::Audio, AudioContent>, // Insert new message types before this line - makeUnknown + &makeUnknown ); std::tie(e->d->msgtype, e->d->content) = delegate(content); } @@ -18,6 +18,11 @@ #pragma once +#include <QtCore/QMetaEnum> +#include <QtCore/QDebug> + +#include <functional> + namespace QMatrixClient { /** @@ -71,14 +76,19 @@ namespace QMatrixClient * @brief Lookup a value by a key in a varargs list * * This function template takes the value of its first argument (selector) - * as a key and searches for it in the key-value map passed in a varargs list - * (every next pair of arguments forms a key-value pair). If a match is found, - * the respective value is returned; if no pairs matched, the last value - * (fallback) is returned. + * as a key and searches for it in the key-value map passed in + * a parameter pack (every next pair of arguments forms a key-value pair). + * If a match is found, the respective value is returned; if no pairs + * matched, the last value (fallback) is returned. * * All options should be of the same type or implicitly castable to the - * type of the first option. Note that pointers to methods of different - * classes are of different object types, in particular. + * type of the first option. If you need some specific type to cast to + * you can explicitly provide it as the ValueT template parameter + * (e.g. <code>lookup<void*>(parameters...)</code>). Note that pointers + * to methods of different classes and even to functions with different + * signatures are of different types. If their return types are castable + * to some common one, @see dispatch that deals with this by swallowing + * the method invocation. * * Below is an example of usage to select a parser depending on contents of * a JSON object: @@ -91,7 +101,7 @@ namespace QMatrixClient * } * * The implementation is based on tail recursion; every recursion step - * removes 2 arguments (match and option). There's no selector value for the + * removes 2 arguments (match and value). There's no selector value for the * fallback option (the last one); therefore, the total number of lookup() * arguments should be even: selector + n key-value pairs + fallback * @@ -99,20 +109,128 @@ namespace QMatrixClient * (the first parameter) - most likely it won't do what you expect because * of shallow comparison. */ + template <typename ValueT, typename SelectorT> + ValueT lookup(SelectorT/*unused*/, ValueT&& fallback) + { + return std::forward<ValueT>(fallback); + } + template <typename ValueT, typename SelectorT, typename KeyT, typename... Ts> - ValueT lookup(SelectorT selector, KeyT key, ValueT value, Ts... remainingMapping) + ValueT lookup(SelectorT&& selector, KeyT&& key, ValueT&& value, Ts&&... remainder) { if( selector == key ) - return value; + return std::forward<ValueT>(value); // Drop the failed key-value pair and recurse with 2 arguments less. - return lookup(selector, remainingMapping...); + return lookup<ValueT>(std::forward<SelectorT>(selector), + std::forward<Ts>(remainder)...); } - template <typename SelectorT, typename ValueT> - ValueT lookup(SelectorT/*unused*/, ValueT fallback) + /** + * A wrapper around lookup() for functions of different types castable + * to a common std::function<> form + * + * This class uses std::function<> magic to first capture arguments of + * a yet-unknown function or function object, and then to coerce types of + * all functions/function objects passed for lookup to the type + * std::function<ResultT(ArgTs...). Without Dispatch<>, you would have + * to pass the specific function type to lookup, since your functions have + * different signatures. The type is not always obvious, and the resulting + * construct in client code would almost always be rather cumbersome. + * Dispatch<> deduces the necessary function type (well, almost - you still + * have to specify the result type) and hides the clumsiness. For more + * information on what std::function<> can wrap around, see + * https://cpptruths.blogspot.jp/2015/11/covariance-and-contravariance-in-c.html + * + * The function arguments are captured by value (i.e. copied) to avoid + * hard-to-find issues with dangling references in cases when a Dispatch<> + * object is passed across different contexts (e.g. returned from another + * function). + * + * \tparam ResultT - the desired type of a picked function invocation (mandatory) + * \tparam ArgTs - function argument types (deduced) + */ + template <typename ResultT, typename... ArgTs> + class Dispatch { - return fallback; + // We take a chapter from functional programming here: Dispatch<> + // uses a function that in turn accepts a function as its argument. + // The sole purpose of the outer function (initialized by + // a lambda-expression in the constructor) is to store the arguments + // to any of the functions later looked up. The inner function (its + // type is defined by fn_t alias) is the one returned by lookup() + // invocation inside to(). + // + // It's a bit counterintuitive to specify function parameters before + // the list of functions but otherwise it would take several overloads + // here to match all the ways a function-like behaviour can be done: + // reference-to-function, pointer-to-function, function object. For + // each of these overloads, we'd have to carefully retrieve the list + // of parameters and cover up possible reference-vs-value + // incompatibilities. Instead, you show what you have and if it's + // possible to bring all your functions to the same std::function<> + // based on what you have as parameters, the code will compile. If + // it's not possible, modern compilers are already good enough at + // pinpointing a specific place where types don't match. + using fn_t = std::function<ResultT(ArgTs...)>; + public: + explicit Dispatch(ArgTs&&... args) + : boundArgs([=](fn_t &&f) { + return f(std::forward<ArgTs...>(args)...); + }) + { } + + template <typename... LookupParamTs> + ResultT to(LookupParamTs&&... lookupParams) + { + // Here's the magic, two pieces of it: + // 1. Specifying fn_t in lookup() wraps all functions in + // \p lookupParams into the same std::function<> type. This + // includes conversion of return types from more specific to more + // generic (because std::function is covariant by return types and + // contravariant by argument types (see the link in the Doxygen + // part of the comments). + // 2. Passing the result of lookup() to boundArgs() invokes the + // lambda-expression mentioned in the constructor, which simply + // invokes this passed function with a set of arguments captured + // by lambda. + return boundArgs( + lookup<fn_t>(std::forward<LookupParamTs>(lookupParams)...)); + } + + private: + std::function<ResultT(fn_t&&)> boundArgs; + }; + + /** + * Dispatch a set of parameters to one of a set of functions, depending on + * a selector value + * + * Use <code>dispatch<CommonType>(parameters).to(lookup parameters)</code> + * instead of lookup() if you need to pick one of several functions returning + * types castable to the same CommonType. See event.cpp for a typical use case. + * + * \see Dispatch + */ + template <typename ResultT, typename... ArgTs> + Dispatch<ResultT, ArgTs...> dispatch(ArgTs&& ... args) + { + return Dispatch<ResultT, ArgTs...>(std::forward<ArgTs...>(args)...); + }; + + // The below enables pretty-printing of enums in logs +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) +#define REGISTER_ENUM(EnumName) Q_ENUM(EnumName) +#else + // Thanks to Olivier for spelling it and for making Q_ENUM to replace it: + // https://woboq.com/blog/q_enum.html +#define REGISTER_ENUM(EnumName) \ + Q_ENUMS(EnumName) \ + friend QDebug operator<<(QDebug dbg, EnumName val) \ + { \ + static int enumIdx = staticMetaObject.indexOfEnumerator(#EnumName); \ + return dbg << Event::staticMetaObject.enumerator(enumIdx).valueToKey(int(val)); \ } +#endif } // namespace QMatrixClient |