3

I have the following code that is meant to accept a Qt QVariant and apply a functor if the variant contains a value:

template<typename T, typename Functor>
inline QVariant map(const QVariant& v, Functor f)
{
    return v.isValid()
            ? QVariant{f(qvariant_cast<T>(v))}
            : v;
}

My problem is that the compiler cannot deduce the type of T when I invoke this function as

map(someFuncReturningQVariant(), [](const QByteArray& array) -> QString {
    return array.toString();
});

The compiler complains (cleaned up from the original error, which has longer type names):

error: no matching function for call to `map(QVariant, <lambda(const QByteArray&)>)`
note: candidate is:
    template<class T, class Functor> QVariant map(const QVariant&, Functor).
note: template argument deduction/substitution failed:
      couldn't deduce template parameter 'T'

This is because QVariant erases the type of the object it contains at runtime. (It still knows it internally, much like boost::any, and qvariant_cast<T>() gets the original object back).

How can I capture the type of the variable passed to the Functor and use it elsewhere? Alternatively, how can I specify that Functor takes a parameter of type T? (I suspect these are actually the same question, or that they at least have the same answer.)

Note that my approach works fine with std::optional, because the types are not erased:

using std::experimental::optional;

template<typename T, typename Functor>
inline auto map(const optional<T>& v, Functor f) -> optional<decltype(f(*v))>
{
    return v ? optional<decltype(f(*v))>{f(*v)}
             : v;
}

Also note that the QVariant code works fine if I manually specify the type:

map<QByteArray>(someFuncReturningQVariant(), [](const QByteArray& array) -> QString {
    return array.toString();
});

But of course, this is much uglier.

Barry
  • 286,269
  • 29
  • 621
  • 977
George Hilliard
  • 15,402
  • 9
  • 58
  • 96

2 Answers2

3

Basically what you want is: given a functor, F, identify the decayed type of its first argument.

To that end, we need function_traits, with which we can do:

template <typename F>
using first_arg = std::decay_t<typename function_traits<F>::template arg<0>::type>;

Which we use:

template<typename Functor>
inline QVariant map(const QVariant& v, Functor f)
{
    using T = first_arg<Functor>;

    return v.isValid()
            ? QVariant{f(qvariant_cast<T>(v))}
            : v;
}
Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Perfect! That's an arcane `using` statement; I wish C++ had something like Rust's `where` clause. Oh well. Is there a canonical `function_traits` or is it just something that people have written because it's useful? – George Hilliard Jul 01 '15 at 21:56
  • 1
    @Barry function_traits is a nice utility. I thought you might be interested to know that it fails for std::bind(f, std::placeholders::_1) – Richard Hodges Jul 01 '15 at 22:09
  • @RichardHodges You can't deduce the argument type of a function template, right? You'd have to provide it. – Barry Jul 01 '15 at 22:20
  • @Barry I think it could be done, decltype(bind(F, Args&&...)) gives the type of the bind object and the type/class of the function object. Then there'd have to be some mpl logic to find the nth placeholder using std::is_placeholder<> and map those back to the arguments of F. Not something I fancy doing right now, it's half past midnight here, but food for thought? – Richard Hodges Jul 01 '15 at 22:36
2
map<QByteArray>(someFuncReturningQVariant(), [](auto&& array){
  return array.toString();
});

is the C++14 way to do that: don't specify the type in the lambda, but rather as (only) a template parameter.

Also note ->QString is redundant in C++11 or 14.

Alternatively, know that QVariant is not a type ameniable to mapping. Do the cast explicitly external to your map function.

Map the QVariant to a optional<T&> externally (or a T*), and return an optional<std::result_of_t<F(T&)>> (or a QVariant if you like throwing away information).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Gotcha. I had stumbled upon this earlier and forgot about it. Lambda type deduction is very cool. And finally, I must return QVariant, because I'm implementing a Qt proxy model. – George Hilliard Jul 01 '15 at 22:14