Функциональное программирование с использованием пакетов параметров в С++

62
6

Это упрощение другой проблемы, которую я имею, но она стоит сама по себе. Идея состоит в том, чтобы реализовать функциональные примитивы, подобные map и apply в Схеме.

Просто повторим: на схеме, заданной функцией f тогда (apply f '(1 2 3)) эквивалентно (f 1 2 3) и (map f '(1 2 3)) эквивалентно ((f 1) (f 2) (f 3)).

Внедрение apply легко, и есть много других вопросов, показывающих, как это делается:

template <class Func, class... Args, std::size_t... Ixs>
auto apply_helper(Func&& func, const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(func(get<Ixs>(args)...))
{
return forward<Func>(func)(get<Ixs>(forward<const tuple<Args...>&>(args))...);
}

template <class Func, class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto apply(Func&& func, const tuple<Args...>& args)
-> decltype(apply_helper(func, args, Ixs()))
{
return apply_helper(forward<Func>(func),
forward<const tuple<Args...>&>(args), Ixs());
}

void print3(int x, const char* s, float f) {
cout << x << "," << s << "," << f << endl;
}

int main() {
auto args = make_tuple(2, "Hello", 3.5);
apply(print3, args);
}

Теперь идет реализация map, которая немного сложнее. Мы хотим, чтобы что-то подобное работало, так что это цель (здесь, используя mapcar чтобы избежать конфликта с std::map):

template <class Type>
bool print1(Type&& obj) {
cout << obj;
return true;
}

int main() {
auto args = make_tuple(2, "Hello", 3.5);
mapcar(print1, args);
}

Другие альтернативы для передачи функции print1 также хорошо.

Итак, если мы скомпрометируем функцию, следующий код будет работать нормально:

template <class... Args, std::size_t... Ixs>
auto mapcar_helper(const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(make_tuple(print1(get<Ixs>(args))...))
{
return make_tuple(print1(get<Ixs>(forward<const tuple<Args...>&>(args)))...);
}

template <class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(const tuple<Args...>& args)
-> decltype(mapcar_helper(args, Ixs()))
{
return mapcar_helper(forward<const tuple<Args...>&>(args), Ixs());
}

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

Мы хотели бы сделать вызов mapcar выше эквивалентного коду:

make_tuple(print1(2), print1("Hello"), print1(3.5));

Обновление. Одна из первых проблем заключалась в том, чтобы заставить его работать с компилятором C++ 11, частично потому, что я использую GCC 4.8, но также потому, что хочу исследовать, как это сделать. Основываясь на комментариях, ниже приведен пример того, как это можно сделать без помощи полиморфных лямбда (которые требуют поддержки C++ 14 компилятора).

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

template <class Func, class... Args, std::size_t... Ixs>
auto mapcar_helper(Func&& func, const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(make_tuple(func(get<Ixs>(args))...))
{
return make_tuple(func(get<Ixs>(args))...);
}

template <class Func, class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(Func&& func, const tuple<Args...>& args)
-> decltype(mapcar_helper(func, args, Ixs())
{
return mapcar_helper(forward<Func>(func), forward<decltype(args)>(args), Ixs());
}

Чтобы иметь возможность передать шаблон "функция", нам нужно обернуть его в объект:

struct print1 {
template <class Type> const Type& operator()(Type&& obj) {
std::cout << obj << " ";
return obj;
}
};

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

   mapcar(print1(), make_tuple(2, "Hello", 3.5));

спросил(а) 2015-12-10T19:35:00+03:00 4 года, 3 месяца назад
1
Решение
72

template <typename F, class... Args, std::size_t... Ixs>
auto mapcar_helper(F f, const tuple<Args...>& args,
index_sequence<Ixs...>)
-> decltype(make_tuple(f(get<Ixs>(args))...))
{
return make_tuple(f(get<Ixs>(args))...);
}

template <typename F, class... Args,
class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(F f, const tuple<Args...>& args)
-> decltype(mapcar_helper(move(f), args, Ixs()))
{
return mapcar_helper(move(f), args, Ixs());
}

Затем вы выполните:

mapcar([](auto&& obj) { return print1(std::forward<decltype(obj)>(obj)); }, args);

Возможно, я не понял этого вопроса. Вам нужно обернуть print1 в лямбда, потому что это неоднозначно; какой экземпляр print1 вы хотели передать?

Если у вас нет макрофобии, вы можете сделать это более элегантным, используя макрос:

#define LIFT(F) ([&](auto&&... args) -> decltype(auto) { \
return F(::std::forward<decltype(args)>(args)...); \
})

Затем вы можете использовать mapcar(LIFT(print1), args).

Вот как я мог бы написать свою собственную функцию map:

template<typename F, class Tuple, std::size_t... Is>
auto map(Tuple&& tuple, F f, std::index_sequence<Is...>)
{
using std::get;
return std::tuple<decltype(f(get<Is>(std::forward<Tuple>(tuple))))...>{
f(get<Is>(std::forward<Tuple>(tuple)))...
};
}

template<typename F, class Tuple>
auto map(Tuple&& tuple, F f)
{
using tuple_type = std::remove_reference_t<Tuple>;
std::make_index_sequence<std::tuple_size<tuple_type>::value> seq;
return (map)(std::forward<Tuple>(tuple), std::move(f), seq);
}

ответил(а) 2015-12-10T19:44:00+03:00 4 года, 3 месяца назад
75

Что я упустил?

#include <iostream>
#include <string>

template<class F, class...Args>
void map(F&& f, Args&&...args)
{
using expander = int[];
(void) expander { 0, ((void) f(args), 0)... };
}

auto main() -> int
{
using namespace std;

map([](const auto& x) { cout << x << endl; }, 1, "hello"s, 4.3);

return 0;
}

ожидаемый результат:

1
hello
4.3

Обратите внимание, что в С++ 17 функция map() становится более приятной:

template<class F, class...Args>
void map(F&& f, Args&&...args)
{
(f(args), ...);
}

Если ваш следующий вопрос: "почему скобки?". Ответ заключается в том, что складывающиеся выражения оцениваются только в контексте выражения. f(arg1), f(arg2); это утверждение.

ссылка: http://en.cppreference.com/w/cpp/language/fold

ответил(а) 2015-12-10T20:28:00+03:00 4 года, 3 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема