How to deduce the return type of a function object from parameters list?

Marius Bancila picture Marius Bancila · Oct 15, 2014 · Viewed 10.8k times · Source

I'm trying to write a projection function that could transform a vector<T> into a vector<R>. Here is an example:

auto v = std::vector<int> {1, 2, 3, 4};
auto r1 = select(v, [](int e){return e*e; }); // {1, 4, 9, 16}
auto r2 = select(v, [](int e){return std::to_string(e); }); // {"1", "2", "3", "4"}

First attempt:

template<typename T, typename R>
std::vector<R> select(std::vector<T> const & c, std::function<R(T)> s)
{
   std::vector<R> v;
   std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
   return v;
}

But for

auto r1 = select(v, [](int e){return e*e; });

I get:

error C2660: 'select' : function does not take 2 arguments

I have to explicitly call select<int,int> to work. I don't like this because the types are redundant.

auto r1 = select<int, int>(v, [](int e){return e*e; }); // OK

Second attempt:

template<typename T, typename R, typename Selector>
std::vector<R> select(std::vector<T> const & c, Selector s)
{
   std::vector<R> v;
   std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
   return v;
}

The result is same error, function does not take 2 arguments. In this case I actually have to supply a 3rd type argument:

auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });

Third attempt:

template<typename T, typename R, template<typename, typename> class Selector>
std::vector<R> select(std::vector<T> const & c, Selector<T,R> s)
{
   std::vector<R> v;
   std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
   return v;
}

For

auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });

the error is:

'select' : invalid template argument for 'Selector', class template expected

For

auto r1 = select(v, [](int e){return e*e; });

error C2660: 'select' : function does not take 2 arguments

(I know the last two attempts are not particularly great.)

How can I write this select() template function to work for the sample code I put in the beginning?

Answer

Piotr Skotnicki picture Piotr Skotnicki · Oct 15, 2014

Option #1:

Basic decltype() usage:

template <typename T, typename F>
auto select(const std::vector<T>& c, F f)
    -> std::vector<decltype(f(c[0]))>
{
    using R = decltype(f(c[0]));
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
    return v;
}

Option #2:

Basic std::result_of<T> usage:

template <typename T, typename F, typename R = typename std::result_of<F&(T)>::type>
std::vector<R> select(const std::vector<T>& c, F f)
{
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
    return v;
}

Option #3:

Advanced decltype() usage and perfect-forwarding (see notes*):

template <typename T, typename A, typename F>
auto select(const std::vector<T, A>& c, F&& f)
    -> std::vector<typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type>
{
    using R = typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type;
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c)
                 , std::back_inserter(v)
                 , std::forward<F>(f));
    return v;
}

Option #4:

Advanced std::result_of<T> usage and perfect-forwarding (see notes*):

template <typename T, typename A, typename F, typename R = typename std::decay<typename std::result_of<typename std::decay<F>::type&(typename std::vector<T, A>::const_reference)>::type>::type>
std::vector<R> select(const std::vector<T, A>& c, F&& f)
{
    std::vector<R> v;
    std::transform(std::begin(c), std::end(c)
                 , std::back_inserter(v)
                 , std::forward<F>(f));
    return v;
}

* Note: Options #3 and #4 assume that the std::transform algorithm takes a function object by-value, and then uses it as a non-const lvalue. This is why one can see this strange typename std::decay<F>::type& syntax. If the function object is supposed to be called within the select function itself, and the result type is not going to be used as a container's template argument (for the purpose of what the outer-most std::decay<T> is used), then the correct and portable syntax for obtaining the return type is:

/*#3*/ using R = decltype(std::forward<F>(f)(*c.begin()));

/*#4*/ typename R = typename std::result_of<F&&(typename std::vector<T, A>::const_reference)>::type