aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2017-05-18 03:37:06 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2017-05-22 09:39:56 +0900
commitbb26ca86ad350f2562b51284e7c631b1e4f77106 (patch)
tree8fe867870d8e8a8488ece979634035109e39e5eb
parentced7a66686596e74a1f25b5d9634b9b562870943 (diff)
downloadlibquotient-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.cpp27
-rw-r--r--events/roommessageevent.cpp20
-rw-r--r--util.h144
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);
}
diff --git a/util.h b/util.h
index 79f76860..f2a7b267 100644
--- a/util.h
+++ b/util.h
@@ -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