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()};});
source to share
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
.
source to share
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;
}
source to share