From: Manuel Nickschas Date: Mon, 15 Oct 2018 22:39:31 +0000 (+0200) Subject: funchelpers: Add a way to invoke a callable with a list of arguments X-Git-Tag: test-travis-01~105 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=0859ff9b7f8633925dde6482d57774f229a937a6;hp=03ee70025a98912c163155955fe3cc3045f629b9 funchelpers: Add a way to invoke a callable with a list of arguments In various places, we serialize function arguments as a QVariantList, and later use QMetaMethod and friends to call a slot with that list. In order to avoid this kind of runtime method lookup, we need a way to directly invoke a given callable (e.g. a function pointer) with a list of serialized arguments. We'll also want to use the type-based conversion functionality of QVariant instead of using undocumented methods and reinterpret_cast'ing raw memory, as is currently the case. Provide a method invokeWithArgsList() that takes any callable and a list of arguments serialized in a QVariantList, performs some runtime sanity checks (argument count and types), and invokes the callable with the arguments converted to the correct type, if possible. Also add a test case for this function. --- diff --git a/src/common/funchelpers.h b/src/common/funchelpers.h index c56cdb94..fcd5aa47 100644 --- a/src/common/funchelpers.h +++ b/src/common/funchelpers.h @@ -20,8 +20,16 @@ #pragma once +#include #include #include +#include +#include + +#include +#include + +// ---- Function traits -------------------------------------------------------------------------------------------------------------------- namespace detail { @@ -62,3 +70,51 @@ struct FuncHelper : public FuncHelper */ template using FunctionTraits = detail::FuncHelper; + +// ---- Invoke function with argument list ------------------------------------------------------------------------------------------------- + +namespace detail { + +// Helper for unpacking the argument list via an index sequence +template::ArgsTuple> +bool invokeWithArgsList(const Callable& c, const QVariantList& args, std::index_sequence) +{ + // Sanity check that all types can be converted + std::array::value> convertible{{args[Is].canConvert>>()...}}; + for (size_t i = 0; i < convertible.size(); ++i) { + if (!convertible[i]) { + qWarning() << "Cannot convert parameter" << i << "from type" << args[static_cast(i)].typeName() << "to expected argument type"; + return false; + } + } + + // Invoke callable + c(args[Is].value>>()...); + return true; +} + +} // detail + +/** + * Invokes the given callable with the arguments contained in the given variant list. + * + * The types contained in the given QVariantList are converted to the types expected by the callable. + * If the conversion fails, or if the argument count does not match, this function returns false and + * the callable is not invoked. + * + * @param c Callable + * @param args Arguments to be given to the callable + * @returns true if the callable could be invoked with the given list of arguments + */ +template +bool invokeWithArgsList(const Callable& c, const QVariantList& args) +{ + using ArgsTuple = typename FunctionTraits::ArgsTuple; + constexpr auto tupleSize = std::tuple_size::value; + + if (tupleSize != args.size()) { + qWarning().nospace() << "Argument count mismatch! Expected: " << tupleSize << ", actual: " << args.size(); + return false; + } + return detail::invokeWithArgsList(c, args, std::make_index_sequence{}); +} diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 2b6b2edf..6b846468 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -1,5 +1,7 @@ quassel_add_test(ExpressionMatchTest) +quassel_add_test(FuncHelpersTest) + quassel_add_test(SignalProxyTest LIBRARIES Quassel::Test::Util diff --git a/tests/common/funchelperstest.cpp b/tests/common/funchelperstest.cpp new file mode 100644 index 00000000..14a78370 --- /dev/null +++ b/tests/common/funchelperstest.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2005-2018 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "testglobal.h" + +#include + +#include "funchelpers.h" + +TEST(FuncHelpersTest, invokeWithArgsList) +{ + int intVal{}; + QString stringVal{}; + + auto callable = [&intVal, &stringVal](int i, const QString& s) + { + intVal = i; + stringVal = s; + }; + + // Good case + { + QVariantList argsList{42, "Hello World"}; + ASSERT_TRUE(invokeWithArgsList(callable, argsList)); + EXPECT_EQ(42, intVal); + EXPECT_EQ("Hello World", stringVal); + } + + // Too many arguments + { + QVariantList argsList{23, "Hi Universe", 2.3}; + ASSERT_FALSE(invokeWithArgsList(callable, argsList)); + // Values shouldn't have changed + EXPECT_EQ(42, intVal); + EXPECT_EQ("Hello World", stringVal); + } + + // Too few arguments + { + QVariantList argsList{23}; + ASSERT_FALSE(invokeWithArgsList(callable, argsList)); + // Values shouldn't have changed + EXPECT_EQ(42, intVal); + EXPECT_EQ("Hello World", stringVal); + } + + // Cannot convert argument type + { + // Ensure type cannot be converted + QVariantList wrong{"Foo", "Bar"}; + QVariant v{wrong}; + ASSERT_FALSE(v.canConvert()); + + QVariantList argsList{23, wrong}; + ASSERT_FALSE(invokeWithArgsList(callable, argsList)); + // Values shouldn't have changed + EXPECT_EQ(42, intVal); + EXPECT_EQ("Hello World", stringVal); + } +}