Можно ли разрешить использование одного типа std:: function lambdas с разными сигнатурами
У меня есть функция более высокого порядка map
, которая похожа на STL for_each
и сопоставляет объект std::function
над vector
вещей.
template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
vector<U> ret;
for (auto &x: xs)
ret.push_back(f(x));
return ret;
}
Теперь я хочу, чтобы эта функция более высокого порядка принимала оба объекта типов function<int (const vector<T>&)>
и function<int (vector<T>)>
, как показано в прилагаемом минимальном примере.
Проблема заключается в том, что function<int (const vector<T>&)>
и function<int (vector<T>)>
кажутся конвертируемыми друг к другу (см. head
и head2
), но map
не будет принимать версию ссылок const function<int (const vector<int>&)>
(см. Q1
).
Можно указать map
принять версию ссылки const с явным преобразованием (Q2
), но это довольно громоздко.
Мне было интересно, можно ли вообще написать функцию deref
, которая удаляет константную ссылку из function<int (const vector<T>&)>
и возвращает function<int (vector<T>)>
?
(Если выше возможно, тогда мне не придется писать две идентичные перегрузки/реализации карты для const refs).
Спасибо.
#include <vector>
#include <functional>
using namespace std;
template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
vector<U> ret;
for (auto &x: xs)
ret.push_back(f(x));
return ret;
}
int main() {
vector<vector<int>> m;
function<int (const vector<int>&)> head = [](const vector<int>& a) {return a[0];};
function<int (const vector<int>&)> head1 = [](vector<int> a) {return a[0];}; //conversion OK
function<int (vector<int>)> head2 = [](const vector<int>& a) {return a[0];}; //conversion OK
map(head2,m); //OK
map(head,m); //Q1: problem line, implicit conversion NOT OK
map(function<int (vector<int>)>(head),m); //Q2: explicit conversion OK
map(deref(head),m); //Q3: ??How-to, deref takes a std::function f and returns a function with const ref removed from its signature
return 0;
}
--- EDIT ---
Меня особенно интересует подобная функция deref
или мета-функция, которая может удалить const ref из сигнатуры типа объекта std::function
, так что я могу как минимум сделать Q2
автоматически.
Я знаю, что, как правильно указывали @Brian и @Manu, использование std::function
для указания типов не является обычным, но мне интересно, что я спросил выше, даже выполнимо. Лично я думаю, что код с std::function
имеет большую ясность, учитывая, как в С# используются общие типы функций Func<T1, T2, T3, ...,Tn, Tresult>
. Это, если стоимость стирания типа допустима.
Я полностью согласен с тем, что С++ может выводить типы возвращаемых данных и давать сообщение об ошибке, если тип неверен. Возможно, это просто вопрос вкуса, и я бы предпочел записать его при написании сигнатур функций.
Ответы
Ответ 1
Я понимаю, почему вы используете std::function
: вам нужно знать тип возвращаемого преобразования для создания вектора, не так ли?
Но рассмотрим совершенно другой подход. Учитывая metafunction std::result_of
, вы можете вычислить тип результата вызова функции, поэтому просто напишите:
template<typename F , typename CONTAINER , typename T = typename std::result_of<F(typename CONTAINER::value_type)>::type>
std::vector<T> map( F f , CONTAINER&& container )
{
std::vector<T> result;
for( auto& e : container )
result.emplace_back( f( e ) );
return result;
}
Преимущества:
-
Не злоупотреблять std::function
: всегда думайте, что делает std::function
(т.е. стирает тип), не используйте его как универсальный тип функции.
-
Полагайтесь на утиную печать вместо привязки к типам. Не беспокойтесь, если что-то не так, она не будет компилировать ни один.
-
Работает для любого стандартного библиотечного контейнера, так как мы извлекли тип элемента с помощью признака value_type
, вместо прямого использования std::vector
.
-
Код намного более ясный и эффективный, так как сокращение использования std::function
.
Относительно вопроса "Возможно ли написать функцию, которая принимает лямбды нескольких подписей?"
Используя std::function
, вы можете написать что-то похожее на Boost.OverloadedFunction в нескольких строках:
template<typename F , typename... Fs>
struct overloaded_function : public std_function<F> , public std_function<Fs>...
{
overloaded_function( F&& f , Fs&&... fs ) :
std_function<F>{ f },
std_function<Fs>{ fs }...
{}
};
Где std_function
является метафоном, который задает тип функции F
возвращает экземпляр std::function
с сигнатурой F
. Я оставляю это как игру/вызов для читателя.
Вот и все. Улучшите его с помощью make-like функции:
template<typename F , typename... Fs>
overloaded_function<F,Fs...> make_overloaded_function( F&& f , Fs&&... fs )
{
return { std::forward<F>( f ) , std::forward<Fs>( fs )... };
}
И вы готовы пойти:
auto f = make_overloaded_function( [](){ return 1; } ,
[](int,int){ return 2; } ,
[](const char*){ return 3; } );
f(); //Returns 1
f(1,2); //Returns 2
f("hello"); //Returns 3
EDIT: "Спасибо. Но то, что я действительно ищу, является мета-функцией, которая берет подпись вызываемого и удаляет const refs из подписи".
Хорошо, позвольте мне попробовать: metafunction std::decay
применяет затухание, выполняемое при передаче аргументов по значению данному типу. Это включает в себя удаление cv-классификаторов, удаление ссылок и т.д. Таким образом, metafunction, подобный вашему, может быть чем-то, что принимает тип сигнатуры функции и применяет разложение ко всем его требованиям:
template<typename F>
struct function_decay;
template<typename R typename... ARGS>
struct function_decay<R(ARGS...)>
{
using type = R(typename std::decay<ARGS>::type...);
};
Это должно делать работу.
Я написал это, потому что вы явно просили его в комментарии, но я настоятельно рекомендую вам использовать альтернативу, которую я показал вам изначально, потому что она имеет много преимуществ по сравнению с вашим путем.
Тем не менее, я надеюсь, что этот ответ помог решить вашу проблему.
Ответ 2
Идиоматическое решение состоит в том, чтобы просто позволить map
взять произвольный функциональноподобный тип,
template<class T, class F>
auto map(F f, vector<T> xs) -> vector<typename result_of<F(T)>::type> {
vector<typename result_of<F(T)>::type> ret;
for (auto &x: xs)
ret.push_back(f(x));
return ret;
}
Основная проблема с этим подходом заключается в том, что вы получаете путаные сообщения об ошибках, если F
не вызываются с аргументами типа T
, или если он возвращает что-то странное, например void
.
(Вторая проблема заключается в том, что первый аргумент map
не может быть перегруженной функцией, компилятор не сможет просто выбрать перегрузку, которая принимает аргумент типа T
.)
(Вы также можете рассмотреть возможность разложения возвращаемого типа F
.)