Type usage that depends on lambda function as return type

I want to make a function that takes a lambda parameter as a parameter and returns an object whose type depends on the return type of the lambda function. What I'm trying to achieve is essentially not an explicit template parameter when instantiated.

Now, here is my solution and my question: is there a shorter (and more elegant) way to do this?

template<typename Func, typename RT = std::unordered_map<int,
  decltype(((Func*)nullptr)->operator()(T())) > >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(mData.second)});
  return r;
}

      

To make it clearer, a lambda type Func

takes T&

as a parameter and returns a vector of a specific type, and mapResult

maps the result Func

to unordered_map

whose _Ty

template parameter is the return type of the lambda function (potentially something different, but still dependent on that type). The actual code is much more complex, but I'm trying to get clarity on this issue specifically.

The only solution I have found to write the type multiple times RT

is to put it in the template parameter list and give it a default value depending on the first template parameter (which itself is inferred from the function argument). This is a bit like defining a template name.

I am using VC12 but I want to have portable code that compiles under g ++ as well.

Then the instance looks like this (dummy example):

auto r = c.mapResult([](T &t){return std::vector<int> {(int)t.size()};});

      

+3


source to share


2 answers


The C ++ 11 Standard Library contains a meta-trick called result_of

. This metafunction calculates the return type of the function object. Probably because of its history in boost (and C ++ 03), it is used in a rather peculiar way: you pass it the type of the function object and the type of arguments you want to call the function object using the combined function type. For example:

struct my_function_object
{
    bool operator()(int);
    char operator()(double);
};

std::result_of<my_function_object(int)>::type // yields bool
std::result_of<my_function_object(short)>::type // yields bool
std::result_of<my_function_object(double)>::type // yields char

      

result_of

performs overload resolution. If you call short s{}; my_function_object{}(s);

, overload resolution will choose my_function_object::operator()(int)

. Hence the corresponding result_of<my_function_object(short)>::type

gives bool

.

Using this trait, you can simplify the computation of the return type as follows:

template<typename Func, typename RT = std::unordered_map<int,
  typename std::result_of<Func(T&)>::type > >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

      

The parameter T&

tells to result_of

use the lvalue argument when resolving the overload. The default (for a non-reference type T

) is xvalue ( T&&

).

There is one slight difference from the OP's version: SFINAE will probably not work correctly using std::result_of

(in C ++ 11). This was resolved in C ++ 14. See N3462 .


C ++ 14 introduced standardized alias patterns such as result_of_t

so you can get rid of typename

and ::type

:

template<typename Func, typename RT = std::unordered_map<int,
  std::result_of_t<Func(T&)> > >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

      

If you are using Visual Studio 2013 or newer, you can write alias templates yourself. You can also take it a step further and record the entire return type as a metafile:

template<typename FT> using result_of_t = typename std::result_of<FT>::type;
template<typename Func> using RetType =
    std::unordered_map<int, result_of_t<Func(T&)> >;

template<typename Func, typename RT = RetType<Func> >
RT mapResult(Func func)
{
  RT r;
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

      


Of course, if you have sufficient support for the mainstream C ++ 14 language (not in VS12), you can also use return type inference:

template<typename Func>
auto mapResult(Func func)
{
  auto r = std::unordered_map<int, result_of_t<Func(T&)>>{};
  for (auto &i : mData)
    r.insert({i.first, func(i.second)});
  return r;
}

      




You can also shorten the version with decltype

:

using std::declval;
decltype(declval<Func>(T{}))

      

although this is not entirely correct, both the function object and the argument will be lvalues:

decltype(declval<Func&>(declval<T&>{}))

      

declval

will use the x value in overload resolution for a non-reference type X

. With the addition &

, we'll tell the lvalue to be used instead. ( result_of

based on declval

, so both show this behavior.)


Note that it would be helpful to start the type result_of_t<Func(T&)>

with a metafile anyway std::decay

to get rid of eg. links that appear in cases such as:

[](string const& s) -> string const& { return s; } // identity

      

It only depends on your use case, and any choice should be documented.


IIRC, is emplace

slightly more efficient (in theory) in this situation (inserting unique elements):

r.emplace(i.first, func(i.second));

      

It may be possible to further optimize this function, for example. by booking the bucket count before insertion, or perhaps using an iterator adapter to use the constructor for insertion. The use std::transform

should also be possible, although I suppose it may not be as effective due to the extra moves of a pair value_type

.

+9


source


There are several things in C ++ 11 you can do to make it a sorter. One way is to use template aliases:

namespace details
{
template <typename Func>
using map_type_t = 
    std::unordered_map<int, typename std::result_of<Func(T)>::type>>;
}

template <typename Func>
details::map_type_t<Func> mapResult(Func func)
{
    details::map_type_t<Func> r;
    //...
    return r;
}

      



In C ++ 14, you can leave return type inference to the compiler:

template <typename Func>
auto mapResult(Func func)
{
    std::unordered_map<int, decltype(func(T()))> r;
    //...
    return r;
}

      

+4


source







All Articles