2015-06-08 2 views
3

Я хочу создать функцию, которая принимает параметр лямбда в качестве параметра, и возвращает объект, тип которого зависит от типа возврата лямбда-функции. То, что я пытаюсь достичь, по сути, не является явным параметром шаблона при создании экземпляра.Использование типа, который зависит от функции лямбда как возвращаемого типа

На данный момент это мое решение, и мой вопрос: есть ли более короткий (и более элегантный) способ сделать это?

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

Чтобы сделать его немного более ясно, тип лямбды Func принимает T& в качестве параметра и возвращает вектор определенного типа, и mapResult отображает результат func в параметре шаблона unordered_map которого _Ty функция лямбды (возможно, что-то еще, но все еще зависит от этого типа). Фактический код намного сложнее, но я пытаюсь получить ясность в этом вопросе конкретно.

Единственное решение, которое я нашел, чтобы избежать записи типа RT несколько раз, заключалось в том, чтобы поместить его в список параметров шаблона и присвоить ему значение по умолчанию, зависящее от первого параметра шаблона (который сам выводится из аргумента функции). Это немного похоже на определение шаблонного имени.

Я использую VC12, но хочу иметь переносимый код, который также компилируется под g ++.

инстанциация тогда выглядит следующим образом (фиктивный пример):

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

* «Если я удалю один набор круглых скобок из объекта decltype, это не сработает» * Какой набор круглых скобок вы говорите? – dyp

+0

@ dyp Вероятно, ошибка в моем чтении. Из этого кода вы не можете удалить скобки. Я не уверен, что у моего исходного кода был дополнительный набор круглых скобок. Редактировать: удалил вопрос. – maxbc

+0

Листинг '((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

ответ

9

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.

+0

Спасибо за все детали! О emplace, спасибо, что я не знал об этом (я все еще не читал книгу на C++, поэтому я возьму всю помощь, которую смогу получить, пока не сделаю это :)). О std :: decay: все возвращения лямбда возвращаются на лету (rvalues) или объекты, созданные в лямбда (которые, если я не ошибаюсь, всегда должны возвращаться по значению). Кроме того, псевдонимы шаблонов потенциально позволяют мне удалить возвращаемый тип из списка параметров шаблона (так как я должен писать только небольшую часть кода), как показано в ответе Veritas. – maxbc

+2

@Veritas Пользователи SO кажутся, что вы сомневаетесь, так же ценны, как и мои, у нас было такое же количество голосов. – dyp

+0

@Veritas жаль ввести вас в заблуждение! Ваш ответ был хорошим дополнением к этому, как я писал в своем последнем комментарии (который так и не сделал его там, я думаю) – maxbc

4

В C++ 11 Есть несколько вещей, которые вы можете сделать, чтобы сделать его сортировщик. Один из способов заключается в использовании шаблонов псевдонимы:

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

В C++ 14 вы можете оставить тип возвращаемого вычет компилятором:

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

Я согласен со вторым решением (но я не могу его использовать), а не с первым, потому что он заставляет вас писать тип дважды. Фактический тип намного длиннее, и я хочу сохранить его в одном месте. – maxbc

+0

Использование псевдонимов на самом деле значительно облегчает удаление дополнительного параметра шаблона (как вы предложили). Я упомянул об этом в своем комментарии выше. – maxbc

Смежные вопросы