Функциональный комбинатор карт C ++ с использованием auto

Я пытаюсь использовать необычные функции -std = c ++ 14 для реализации комбинатора map, который вы видите на функциональных языках (не путать с std :: map). Моя конечная цель — написать заголовок «шаблона фасада» для функционального программирования, который позволит мне забыть о побочных эффектах и ​​итераторах большую часть времени. Я нашел сообщение от единомышленника в https://gist.github.com/phatak-dev/766eccf8c72484ad623b . Мадхукара версия карты выглядит так

template <typename Collection,typename unop>
Collection map(Collection col,unop op) {
std::transform(col.begin(),col.end(),col.begin(),op);
return col;
}

Кажется, он работает идеально, если вы не просите что-то глупое, но тип возвращаемого значения должен совпадать с набором входных данных. Моя попытка обобщить наличие домена и диапазона различных типов заключается в следующем:

template <typename Collection, typename function>
auto map(function f, Collection c)  {
auto result;
std::transform(c.begin(),c.end(),result.begin(),f);
return result;
}

Это не компилируется, но, надеюсь, кому-то понятно, что я пытаюсь сделать … Я хочу инициализировать пустой-же-тип-контейнера-as-c выходных-типа-f, а затем положить f(c[i])в этом. Компилятор жалуется, что объявление ‘auto result’ не имеет инициализатора, но я не знаю, как запросить пустое значение независимо от того, что из этого. Есть ли способ настроить эту строку, чтобы она делала то, что я пытаюсь сделать? Я никогда раньше не пытался сделать что-нибудь такое экзотическое с auto, поэтому любые дополнительные предложения приветствуются.

Спасибо!

Джон

Редактировать: вот пример, который, мы надеемся, чувственный:

auto first_letter = [](string s)  { return s[0]; }
vector<string> words;
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map(first_letter, words); // {'h','w'}

Изменить 2: Вот еще один подход, который использует тяжеловесную библиотеку «потоков» (не путать с потоками ввода-вывода) для реализации «шаблона итератора», такого как потоки Java:

http://jscheiny.github.io/Streams/

Java-подход:
http://tutorials.jenkov.com/java-collections/streams.html

Такой потоковый подход обеспечивает большую свободу выбора типов контейнеров (как, кажется, поддерживают несколько респондентов) и ленивую оценку.

4

Решение

Просто используйте повышение :: адаптеры :: tranformed:

#include <boost/range/adaptor/transformed.hpp>

template <typename Collection, typename function>
auto map(function f, Collection c)  {
return c | boost::adaptors::transformed(f);
}

С этим диапазоном — вы можете создать любой контейнер, который вы хотите.

char first_letter(string s)  { return s[0]; }
vector<string> words;
words.push_back("hello"); words.push_back("world");
auto transformed_range = map(first_letter, words);
vector<char> first_letters(begin(transformed_range ), end(transformed_range ));

Если вы настаиваете на map функция, возвращающая контейнер, а не диапазон — добавьте еще один параметр в этот шаблон функции:

#include <boost/range/adaptor/transformed.hpp>

template <typename Result, typename Collection, typename function>
auto map(function f, Collection c)  {
auto transformed_range = c | boost::adaptors::transformed(f);
return Result(begin(transformed_range), end(transformed_range));
}

char first_letter(string s)  { return s[0]; }
vector<string> words;
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map<vector<char>>(first_letter, words);

Но если вы действительно настаиваете на том, чтобы вести себя точно так, как вы хотите — у вас должны быть некоторые черты, знающие, как преобразовать тип коллекции в другой тип коллекции с преобразованным значением.

Первый — способ получить new_value_type:

template <typename Function, typename OldValueType>
struct MapToTransformedValue
{
using type = decltype(std::declval<Function>()(std::declval<OldValueType>()));
};

Общая черта:

template <typename Function, typename Container>
struct MapToTransformedContainer;

Самый простой случай — для std::array:

// for std::array
template <typename Function, typename OldValueType, std::size_t N>
struct MapToTransformedContainer<Function, std::array<OldValueType, N>>
{
using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
using type =  std::array<value_type, N>;
};

За std::vector — немного сложнее — вам нужно предоставить новый распределитель, для распределителей std — вы можете использовать его шаблон повторной привязки:

// for std::vector
template <typename Function, typename OldValueType, typename OldAllocator>
struct MapToTransformedContainer<Function, std::vector<OldValueType, OldAllocator>>
{
using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
using allocator = typename OldAllocator::template rebind<value_type>::other;
using type =  std::vector<value_type, allocator>;
};

Итак, ваша функция будет выглядеть следующим образом:

template <typename Collection, typename function>
auto map(function f, Collection c)
{
using NewCollectionType = typename MapToTransformedContainer<function, Collection>::type;
auto transformed_range = c | boost::adaptors::transformed(f);
return NewCollectionType (begin(transformed_range), end(transformed_range));
}

Сейчас — твой main() по желанию:

char first_letter(std::string const& s)  { return s[0]; }

int main() {
std::vector<std::string> words;
words.push_back("hello"); words.push_back("world");
auto first_letters = map(first_letter, words);
std::cout << first_letters[0] << std::endl;
}

Остерегайтесь, что для других контейнеров, где value_type состоит из Key,Value пара — как std::map, std::set (и их неупорядоченные _… братья и сестры) вы должны определить другую специализацию MapToTransformedContainer

3

Другие решения

Я бы использовал тот факт, что большинство контейнеры есть конструктор, принимающий пару итераторов.

#include <boost/iterator/transform_iterator.hpp>

template <typename Function, typename Collection>
struct map_to
{
Function f;
const Collection& c;

template <typename T>
operator T() &&
{
using std::begin; using std::end;
return { boost::make_transform_iterator(begin(c), f)
, boost::make_transform_iterator(end(c), f) };
}
};

template <typename Function, typename Collection>
map_to<Function, Collection> map(Function f, const Collection& c)
{
return { f, c };
}

тесты:

int main()
{
std::vector<std::string> words;

words.push_back("hello");
words.push_back("world");

auto first_letter = [](std::string s) { return s[0]; };

std::vector<char> v = map(first_letter, words);
std::set<char> s = map(first_letter, words);
std::forward_list<char> f = map(first_letter, words);
}

DEMO

1

Просто для удовольствия, вот мой путь к этой проблеме:

#include <iostream>
#include <algorithm>
#include <set>
#include <vector>

template <template<typename...> class Collection>
struct mapper {
template<typename function, typename T, typename... Rest>
static auto map(function f, const Collection<T, Rest...>& c)  {
Collection<decltype(f(*c.begin()))> result;
std::transform(c.begin(),c.end(),std::inserter(result, result.end()),f);
return result;
}
};

int main()
{
// Example 1
std::vector<int> v{0, 1};
auto fv = mapper<std::vector>::map([](const auto& val) { return val + 0.1f; }, v);
for (const auto& f : fv) { std::cout << f << " "; }

std::cout << "\n";

// Example 2
std::set<float> m{1, 2, 3, 4};
auto fm = mapper<std::set>::map([](const auto& val) { return static_cast<int>(val / 2.0f); }, m);
for (const auto& f : fm) { std::cout << f << " "; }
}

Обратите внимание, что это будет работать только до тех пор, пока вы довольны значениями по умолчанию для всего, кроме параметра типа выходного контейнера.

0

У меня была та же самая задача, и я был в порядке с компромиссом по функциональности, если это означало уменьшенный шаблон.

Вот что я закончил (может не работать с контейнерами, которые используют более одного явного аргумента или чьи аргументы шаблона не могут быть выведены тривиально, но вы согласитесь, что он чист).

using std::vector;

template<typename Src, typename Dst, template<class, typename ...> typename Container>
Container<Dst> fmap(Container<Src>& container, Dst(*f)(Src)) {
Container<Dst> result;
result.reserve(container.size());
std::transform(container.begin(), container.end(), std::back_inserter(result), f);
return result;
}

int main() {
vector<int> a = {1, 2, 3};
auto f = [](int x) -> double { return (x + 1)/2; };
vector<double> b = fmap(a, +f);
for (auto x: b) {
std::cout << x << std::endl;
}
return 0;
}

Основным требованием для меня было не делать его уродливым, и он не должен использовать повышение.

Изменить: Это также будет вести себя глупо (читай: не компилировать), если вы попытаетесь передать вектор с нестандартным распределителем, скажем.

0