Существует общая абстракция для обоих контейнеров и функций. Я узнал об этом в Haskell, и я пытаюсь реализовать его на C++.перегрузка над типами функций с шаблонами
Большинства C++ программисты знакомы с стандом :: преобразования, грубо говоря, заданную функцию от типа A к B, вы можете преобразовать контейнер типа А в контейнер типа B.
Вы можете преобразовать функции аналогичным образом, если задана функция foo от A до B, вы можете преобразовать функциональную панель с Z на A в функцию foo. бар от Z до B. Реализация проста, это просто композиция.
Я хотел бы определить функцию fmap, на контейнерах и функциях, чтобы отразить эту абстракцию для общего программирования.
Контейнер легко (я знаю, что это не полностью вообще)
template <typename A, typename Func>
auto fmap(Func f, vector<A> in) {
vector<decltype(f(in[0]))> out_terms{};
for(auto vec : in)
out_terms.push_back(f(vec));
return out_terms;
}
Однако аналогичная функция для функций делает меня гораздо более нервной.
template <typename FuncT, typename Func>
auto fmap(FuncT f, Func in) {
return [f, in](auto x){
return f(in(x));
};
}
Хотя шаблон не будет специализироваться ни на чем, кроме вызываемых предметов, я волнуюсь, что это смущает разрешение перегрузки. Я хотел бы ввести ограничения типа для параметров шаблона, чтобы ограничить их разрешение на типы функций, чтобы сохранить пространство имен чистым. И я собирался спросить, как это сделать.
Эта абстракция чрезвычайно общая, есть соответствующие fmaps для указателей на значения, которые, как я подозреваю, могут также конфликтовать.
Итак, я думаю, что мой вопрос: могу ли я иметь две различные реализации шаблонов с одинаковой подписью уровня шаблона? Я почти уверен, что ответ - нет, но может быть, что-то подобное может быть подделано. А если нет, какие инструменты доступны сегодня, чтобы различать перегрузки? Специально для типов функций.
Мне кажется, что это учебник для понятий, хотя я не уверен.
Редактировать: Boost будет приемлемым для использования, и SFINAE в частности. Я пытаюсь найти решение, которое было бы знакомо большинству программистов, и было бы удобно и канонически, насколько это возможно. Я мог бы переименовать fmap для компоновки, но тогда программисту нужно было бы знать, чтобы передать композицию функции шаблона, принимающей fmap. Это было бы неудачно, потому что fmap является семантически уникальным.
Редактировать 2: Тривиальный пример того, как это используется.
template <typename T>
auto double_everything(T in){
auto doublef = [](auto x){return 2*x;};
return fmap(doublef, in);
}
Он обобщает карты над контейнерами на карты по «контейнерам». Таким образом, double_everything(vector<int> {1, 2, 3})
возвращает вектор с удвоенными элементами. Но double_everything([](int x){ return x + 1; })
возвращает функцию, выходные выходы которой в два раза превышают выходы функции приращения. Это похоже на удвоение своего рода списка. У абстракции есть некоторые приятные свойства, я не просто делаю это. Во всяком случае, переименование функции fmap
для составления не отвечает на вопрос.
Edit 3: fmap
для шаблона C
принимает функции от A
к B
функциям от C<A>
до C<B>
и удовлетворяет fmap(compose(f, g) , c) = fmap(f, fmap(g, c))
. Это свойство сохранения хорошей структуры.
Функции, которые делают это для диапазонов, уже существуют разными именами. Но диапазоны не являются единственными шаблонами для типов. Вот fmap
для std::optional
:
template<typename T, typename Func>
auto fmap(Func f, optional<T> o) -> optional<f(*o)>{
if(o)
return f(*o);
else
{};
}
Эта реализация не предполагает каких-либо понятий диапазона вообще, как fmap
для функций, представленных ранее. Но он удовлетворяет семантическим требованиям для fmap
.
Я пытаюсь определить fmap
для разных перегрузок таким же образом. Я бы определил новый operator *
для пользовательского типа матрицы. Поэтому я бы с радостью определил fmap
с точки зрения boost::transform_iterator
. Тогда эти алгоритмы будут работать с функцией generic с точки зрения fmap
.
Вот пример такой функции:
template <
template<typename, typename> class Cont,
typename Fmappable,
typename Alloc,
typename Func>
auto map_one_deep(Func f, Cont<Fmappable, Alloc> c){
auto g = [f](Fmappable x){ return fmap(f, x); };
return fmap(g, c);
}
теперь, если мы пишем
auto lists = vector<vector<int> > { {1, 2, 3}, {4, 5, 6} };
auto lists_squared = map_one_deep([](int x){return x*x;} , lists);
lists_squared
печататься дает
1 4 9
16 25 36
Если мы вместо этого были вектор дополнительных опций, опциональные элементы были бы квадратными, если бы содержали элементы.
Я пытаюсь понять, как следует работать с функциями более высокого порядка в C++.
Довольно точно, что это невозможно сделать в общем случае. Я бы просто переименовал вашу функцию 'compose'. – Barry
Я уже не знаю достаточно C++ для оценки этого сообщения, но я думаю, что это по крайней мере актуально для того, что вы пытаетесь сделать: [The Functor Pattern in C++] (https://www.fpcomplete.com/blog/2012/ 07/the-functor-pattern-in-c) – chepner
Вы можете ограничить тип своего шаблона для своей функции с помощью SFINAE. – Jarod42