C++ 11 Стандартная библиотека содержит metafunction под названием result_of
, Этот metafunction вычисляет возвращаемый тип объекта функции. Вероятно, из-за его истории в boost (и C++ 03) он используется довольно своеобразно: вы передаете ему тип объекта функции и тип аргументов, которые вы хотите вызвать объект функции, с помощью комбинированного функция type. Например:
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
выполняет разрешение перегрузки. Если вы позвоните по номеру short s{}; my_function_object{}(s);
, разрешение перегрузки выберет my_function_object::operator()(int)
. Следовательно, соответствующий result_of<my_function_object(short)>::type
дает bool
.
Используя эту особенность, можно упростить вычисление возвращаемого типа следующим образом:
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;
}
Параметр T&
говорит result_of
использовать Lvalue аргумент в разрешении перегрузки. По умолчанию (для неосновного типа T
) значение xvalue (T&&
).
Существует одно небольшое отличие от версии OP: SFINAE, вероятно, работает неправильно, используя std::result_of
(в C++ 11). Это было разрешено в C++ 14. См. N3462.
C++ 14 ввел стандартные шаблоны псевдонимов, как result_of_t
, так что вы можете избавиться от typename
и ::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;
}
Если вы используете Visual Studio 2013 или новее, вы можете написать алиасы сами. Вы могли бы также пойти на один шаг дальше и писать весь возвращаемый тип как metafunction:
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;
}
Конечно, если у вас есть достаточная поддержка ядра языка C++ 14 (не в VS12), вы можете использовать тип вычет возврата, а также:
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;
}
также можно сократить версию с помощью decltype
:
using std::declval;
decltype(declval<Func>(T{}))
хотя это не совсем верно, как объект функции и аргумент будет именующий:
decltype(declval<Func&>(declval<T&>{}))
declval
будет использовать xvalue в разрешении перегрузки для нереференсного типа X
. Добавляя &
, мы говорим, что вместо этого используйте lvalue. (result_of
основан на declval
, так как показывают это поведение.)
Обратите внимание, что в любом случае, это может быть полезно для запуска result_of_t<Func(T&)>
типа через std::decay
metafunction, чтобы избавиться, например, ссылок, которые фигурируют в таких случаях, как:
[](string const& s) -> string const& { return s; } // identity
Это зависит от вашего варианта использования, хотя и выбор должен быть документирован.
IIRC, emplace
является немного более эффективным (теоретически) в этой ситуации (вставка уникальных элементов):
r.emplace(i.first, func(i.second));
Это могло бы быть возможным для дальнейшей оптимизации этой функции, например, путем резервирования подсчета ведра перед вставкой или, возможно, с помощью адаптера итератора для использования конструктора для вставки. Использование std::transform
также должно быть возможным, хотя я предполагаю, что он не может быть столь же эффективным из-за дополнительных ходов пары value_type
.
* «Если я удалю один набор круглых скобок из объекта decltype, это не сработает» * Какой набор круглых скобок вы говорите? – dyp
@ dyp Вероятно, ошибка в моем чтении. Из этого кода вы не можете удалить скобки. Я не уверен, что у моего исходного кода был дополнительный набор круглых скобок. Редактировать: удалил вопрос. – maxbc
Листинг '((Func *) nullptr)' нуждается в наборе '()', потому что вы используете (C-style) буквенное обозначение. Грамматика просто интерпретирует '(T) e-> x' как' (T) (e-> x) '. Вместо этого вы можете использовать приведение в стиле функции 'T (e) -> x', но это не работает с' Func * ', поскольку это не спецификатор простого типа. Другие роли, такие как 'static_cast (e) -> x', будут работать.Последними скобками являются функции-вызовы-parens 'operator()', плюс парсеры для «cast» (создание временного) типа 'T'. Он может быть записан как 'operator() (T {})' или просто '(* static_cast (nullptr)) (T {})'. –
dyp