4. Теперь можно выводить на экран произвольное количество аргументов с помощью конструкции print_args(cout,1,2,"foo",3,"bar")
<<
, чтобы он работал с кортежами, реализовав шаблонную функцию, которая соответствует любым специализациям для кортежа:template
ostream& operator<<(ostream &os, const tuple
{
5. Сейчас все станет чуть сложнее. Сначала мы воспользуемся лямбда-выражением, которое принимает произвольно большое количество параметров. При вызове выражение добавляет аргумент os
print_args
, передавая полученный новый список аргументов. Это значит, что вызов capt_tup(...некоторые параметры
...) преобразуется в вызов print_args(os, ...некоторые параметры
...). auto print_to_os ([&os](const auto &...xs) {
print_args(os, xs...);
});
6. Теперь можем выполнить распаковку кортежа. Для этого воспользуемся методом std::apply
t = (1,2,3)
и мы вызываем метод apply(capt_tup,t)
, то эти действия приведут к вызову функции capt_tup(1,2,3)
, что, в свою очередь, повлечет вызов print_args(os,1,2,3)
. Это именно то, что нужно. Дополнительно окружим операцию вывода скобками: os << "(";
apply(print_to_os, t);
return os << ")";
}
7. О’кей, мы написали сложный фрагмент кода, который сделает нашу жизнь гораздо проще, если мы захотим вывести кортеж на экран. Но с помощью кортежей мы можем сделать больше. Допустим, напишем функцию, принимающую в качестве аргумента итерабельный диапазон данных, например вектор или список чисел. Эта функция проитерирует по данному диапазону и вернет
template
tuple
sum_min_max_avg(const T ⦥)
{
8. Функция std::minmax_element
std::accumulate
складывает все значения в данном диапазоне. Это все нужно, чтобы вернуть четыре значения, которые позже будут помещены в кортеж! auto min_max (minmax_element(begin(range), end(range)));
auto sum (accumulate(begin(range), end(range), 0.0));
return {sum, *min_max.first, *min_max.second,
sum / range.size()};
}
9. Перед реализацией основной программы создадим последнюю волшебную вспомогательную функцию. Я называю ее волшебной, поскольку она на первый взгляд выглядит очень сложной, но если понять принцип ее работы, она покажется очень удобным помощником. Она сгруппирует два кортежа. Это значит, что если мы передадим ей кортежи (1,2,3)
('a','b','c')
, то она вернет кортеж (1,'a',2,'b',3,'c')
.template
static auto zip(const T1 &a, const T2 &b)
{
10. Мы подобрались к самым сложным строкам данного примера. Создадим объект функции z, принимающий произвольное количество аргументов. Затем он возвращает другой объект функции, который захватывает все эти аргументы в набор параметров xs
xs
и ys
. А теперь взглянем, что именно происходит с этими наборами параметров. Вызов make_tuple(xs,ys)
. группирует наборы параметров поэлементно. Т.е. при наличии наборов xs = 1,2,3
и ys = 'a','b','c'
он вернет новый набор параметров (1,'a')
, (2,'b')
, (3,'c')
. Данный набор представляет собой разделенный запятыми список, состоящий из трех кортежей. Чтобы объединить их в std::tuple_cat
, которая принимает произвольное количество кортежей и упаковывает их в один. Таким образом получим кортеж (1,'a',2,'b',3,'c')
. auto z ([](auto ...xs) {
return [xs...](auto ...ys) {
return tuple_cat(make_tuple(xs, ys) );
};
});