aboutsummaryrefslogtreecommitdiff
path: root/lib/util.h
diff options
context:
space:
mode:
Diffstat (limited to 'lib/util.h')
-rw-r--r--lib/util.h205
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/util.h b/lib/util.h
new file mode 100644
index 00000000..65de0610
--- /dev/null
+++ b/lib/util.h
@@ -0,0 +1,205 @@
+/******************************************************************************
+ * Copyright (C) 2016 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 <QtCore/QMetaEnum>
+#include <QtCore/QDebug>
+
+#include <functional>
+
+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 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. 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:
+ * {@code
+ * auto parser = lookup(obj.value["type"].toString(),
+ * "type1", fn1,
+ * "type2", fn2,
+ * fallbackFn);
+ * parser(obj);
+ * }
+ *
+ * The implementation is based on tail recursion; every recursion step
+ * 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
+ *
+ * @note Beware of calling lookup() with a <code>const char*</code> selector
+ * (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&&... remainder)
+ {
+ if( selector == key )
+ return std::forward<ValueT>(value);
+
+ // Drop the failed key-value pair and recurse with 2 arguments less.
+ return lookup<ValueT>(std::forward<SelectorT>(selector),
+ std::forward<Ts>(remainder)...);
+ }
+
+ /**
+ * 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)
+ */
+#if __GNUC__ < 5 && __GNUC_MINOR__ < 9
+ // GCC 4.8 cannot cope with parameter packs inside lambdas; so provide a single
+ // argument version of Dispatch<> that we only need so far.
+ template <typename ResultT, typename ArgT>
+#else
+ template <typename ResultT, typename... ArgTs>
+#endif
+ class Dispatch
+ {
+ // The implementation takes a chapter from functional programming:
+ // 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. This
+ // probably could be done as well but I preferred a more compact
+ // solution: 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.
+ public:
+#if __GNUC__ < 5 && __GNUC_MINOR__ < 9
+ using fn_t = std::function<ResultT(ArgT)>;
+ explicit Dispatch(ArgT&& arg)
+ : boundArgs([=](fn_t &&f) { return f(std::move(arg)); })
+ { }
+#else
+ using fn_t = std::function<ResultT(ArgTs...)>;
+ explicit Dispatch(ArgTs&&... args)
+ : boundArgs([=](fn_t &&f) { return f(std::move(args)...); })
+ { }
+#endif
+
+ 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).
+ auto fn = lookup<fn_t>(std::forward<LookupParamTs>(lookupParams)...);
+ // 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.
+ if (fn)
+ return boundArgs(std::move(fn));
+
+ // A shortcut to allow passing nullptr for a function;
+ // a default-constructed ResultT will be returned
+ // (for pointers, it will be nullptr)
+ return {};
+ }
+
+ 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
+