3

I'm looking for a generic way to call a std::function with arguments from a QVariantList. This Version works but has the drawback the template parameters must be specified:

template <typename... T>
struct VariantFunc {
    static void Invoke(std::function<void(T...)> f, const QVariantList& args)
    {
        InvokeHelper(f, args);
    }

private:
    template <typename T1>
    static void InvokeHelper(std::function<void(T1)> f, const QVariantList& args)
    {
        f(args.at(0).value<T1>());
    }   
    template <typename T1, typename T2>
    static void InvokeHelper(std::function<void(T1, T2)> f, const QVariantList& args)
    {
        f(args.at(0).value<T1>(), args.at(1).value<T2>());
    }
    template <typename T1, typename T2, typename T3>
    static void InvokeHelper(std::function<void(T1, T2, T3)> f, const QVariantList& args)
    {
        f(args.at(0).value<T1>(), args.at(1).value<T2>(), args.at(2).value<T3>());
    }
};

auto args = QVariantList() << 100 << QString("hello") << QJsonValue(1234);
auto f = [](int i, QString s, QJsonValue j) { qDebug() << i << s << j;  };
VariantFunc<int, QString, QJsonValue>::Invoke(f, args);

I'd like to have such an implementation:

struct VariantFunc2 {
    template<typename Func>
    static void Invoke(Func f, const QVariantList& args)
    {
        // ???
    }
};

auto args = QVariantList() << 100 << QString("hello") << QJsonValue(1234);
auto f = [](int i, QString s, QJsonValue j) { qDebug() << i << s << j;  };
VariantFunc2::Invoke(f, args);

Is there a way to do this in C++11? Obviously the deduction guides for std::function provides a solution, but not with C++11.

AndiR
  • 179
  • 10
  • For the second version, what should happen if `Func` has more than one `operator()`? (or is an overloaded function) – Caleth Apr 24 '18 at 12:16

3 Answers3

1

Here is a C++14 implementation. It does not use std::function, to avoid the overhead associated with said class. Instead it uses perfect forwarding of the callable. Additionally, the Invoke function returns the result of invoking the callable.

template <typename F>
struct function_traits;

template <typename R, class... Args>
struct function_traits<R(*)(Args...)> : public function_traits<R(Args...)> {};

template <typename R, class... Args>
struct function_traits<R(Args...)> {};

template <typename C, class R, class... Args>
struct function_traits<R(C::*)(Args...) const> : public function_traits<R(C&, Args...)>
{
    using args = std::tuple<Args...>;
};

template <typename F, typename Args, std::size_t ... I>
auto Invoke_impl(F&& f, const QVariantList& args, std::index_sequence<I ...>)
{
    return f(args.at(I).value<std::remove_const_t<std::remove_reference_t<std::tuple_element_t<I, Args>>>>() ...);
}

template <typename Func>
auto Invoke(Func&& f, const QVariantList& args)
{
    using F = typename std::remove_reference<Func>::type;
    using Args = typename function_traits<decltype(&F::operator())>::args;
    using Indices = std::make_index_sequence<std::tuple_size<Args>::value>;
    return Invoke_impl<Func, Args>(std::forward<Func>(f), args, Indices{});
}

int main()
{
    QString msg = "From Lambda";
    auto f = [msg](int i, const QString& s, const QJsonValue& j)
    {
        qDebug() << msg << i << s << j;
        return 5;
    };
    auto args = QVariantList() << 11 << "hello" << QJsonValue(123.456);
    qDebug() << Invoke(f, args);
}
Jonas
  • 6,915
  • 8
  • 35
  • 53
  • Why C++14? What's missing in C++11? – AndiR Apr 25 '18 at 13:27
  • At least the auto return type (without a trailing return type) and std::index_sequence and its helper function. And somewhat also std::remove_*_t, but that just reduces typing. – Jonas Apr 25 '18 at 20:03
0

Wrapping it in a class just makes this harder. You want the pack T... to be a template parameter of Invoke

namespace detail {
template <typename... T, std::size_t... I>
void invoke_helper(std::function<void(T...)> f, const QVariantList& args, std::index_sequence<I...>) 
{
    f(args.at(I).value<T>()...);
}

template <typename... T>
void deduce_invoke(std::function<void(T...)> f, const QVariantList& args)
{
    std::index_sequence_for<T...> idxs;
    invoke_helper<T...>(std::move(f), args, idxs);
}

}

template <typename Func>
void Invoke(Func&& f, const QVariantList& args)
{
    detail::deduce_invoke(std::function{f}, args);
}

This will deduce T... for unambiguous cases.

This shares more similarity with std::apply than with std::invoke, so I think Apply would be a better name.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • I think declare the function with template void Invoke(std::function f) does not work since the compiler cannot deduce the template parameters. The right way might be the declaration like template static void Invoke(Func f, const QVariantList& args). – AndiR Apr 24 '18 at 12:25
  • @AndiR You then need a method of extracting `T...` from `Func` – Caleth Apr 24 '18 at 12:29
  • Edit will work in C++17, with the `std::function` deduction guide – Caleth Apr 24 '18 at 12:37
  • Ok, the solution with deduce_invoke would do it. But unfortunatelly I have only C++11 availiable. I guess Qt does something similar in their new signal slot syntax. There is a helper class called QFunctorSlotObject. – AndiR Apr 24 '18 at 12:50
  • Ah, apparently `std::reference_wrapper` will do a similar job as `std::function` here, but you still need to get the `T...` somewhere – Caleth Apr 24 '18 at 12:58
  • Something like https://stackoverflow.com/questions/22630832/get-argument-type-of-template-callable-object – Caleth Apr 24 '18 at 13:00
0

The Key is the correct type deduction from functor.

namespace FunctorHelper {
template <typename R, typename... Args>
struct VariantFunc {
    static R invoke(const std::function<R(Args...)>& f, const QVariantList& args)
    {
        std::index_sequence_for<Args...> idxs;
        return invoke_helper(f, args, idxs);
    }

private:
    template <std::size_t... I>
    static R invoke_helper(const std::function<R(Args...)>& f, const QVariantList& args, std::index_sequence<I...>)
    {
        return f(args.at(I).value<std::remove_const<std::remove_reference<Args>::type>::type>()...);
    }
};

template <typename F>
struct function_traits;

// function pointer
template <typename R, class... Args>
struct function_traits<R(*)(Args...)> : public function_traits<R(Args...)>
{};

template <typename R, class... Args>
struct function_traits<R(Args...)>
{};

// const member function pointer
template <typename C, class R, class... Args>
struct function_traits<R(C::*)(Args...) const> : public function_traits<R(C&, Args...)>
{
    static constexpr std::size_t arity = sizeof...(Args);

    static R deduce_invoke(const std::function<R(Args...)>& f, const QVariantList& args)
    {
        return VariantFunc<R, Args...>::invoke(f, args);
    }
};
} // namespace

template <typename Func>
void Invoke(Func f, const QVariantList& args)
{
    using call_type = FunctorHelper::function_traits<decltype(&Func::operator())>;
    call_type::deduce_invoke(f, args);
}

void main()
{
    QString msg = "From Lambda";
    auto f = [msg](int i, const QString& s, const QJsonValue& j)
    {
        qDebug() << msg << i << s << j;
    };
    auto args = QVariantList() << 11 << "hello" << QJsonValue(123.456);
    Invoke(f, args);
    // "From Lambda" 11 "hello" QJsonValue(double, 123.456)
}
AndiR
  • 179
  • 10
  • That look good. Just out of curiosity, why are `std::remove_const` and `std::remove_reference` needed in `invoke_helper`? Won't that force a copy of the arguments, instead of const references (for some of them)? – Jonas Apr 25 '18 at 08:46
  • 1
    else the const refs in auto f = [msg](int i, const QString& s, const QJsonValue& j) won't work. The QVariant::value() expects a plain Type not a ref and not a const. – AndiR Apr 25 '18 at 09:47