Extracting a template parameter from an arguctor type

I have the following code, which is 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 infer the type of T when I call this function as

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

      

The compiler complains (cleared of 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, how boost::any

, and it qvariant_cast<T>()

returns the original object).

How can I capture the type of the variable passed toFunctor

and use it elsewhere? Alternatively, how can I specify what Functor

the type parameter is taking T

? (I suspect this is actually the same question, or that they at least have the same answer.)

Note that my approach works great 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.

+3


source to share


2 answers


Basically what you want: define a functor F

, define the decomposed type of its first argument.

For this we need function_traits

, with which we can:

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

      



What 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;
}

      

+3


source


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

      

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

Also note which ->QString

is redundant in C ++ 11 or 14.



Alternatively, know what is QVariant

not a matchable type. Make the cast explicitly external to your mapping function.

Match QVariant

with a optional<T&>

from outside (or a T*

) and return optional<std::result_of_t<F(T&)>>

(or a QVariant

if you like to discard information).

+2


source







All Articles