Шрифт:
Интервал:
Закладка:
Функция zip для кортежей
Функция zip принимает два кортежа, но выглядит весьма сложной, несмотря на то что имеет очень четкую реализацию:
template <typename T1, typename T2>
auto zip(const T1 &a, const T2 &b)
{
auto z ([](auto ...xs) {
return [xs...](auto ...ys) {
return tuple_cat(make_tuple(xs, ys) ...);
};
});
return apply(apply(z, a), b);
}
Для лучшего понимания этого кода представьте, что кортеж a содержит значения 1, 2, 3, а кортеж b — значения 'a', 'b', 'c'.
В данном случае вызов apply(z, a) приведет к вызову z(1, 2, 3). Он вернет объект функции, который захватит значения 1, 2, 3 в набор параметров xs. В момент вызова с помощью apply(z(1,2,3),b) этот объект получит значения 'a', 'b', 'c', помещенные в набор параметров ys. По сути, действие аналогично прямому вызову z(1,2,3)('a', 'b', 'c').
О’кей, что произойдет теперь, когда у нас есть значения xs = (1, 2, 3) и ys = ('a', 'b', 'c')? Выражение tuple_cat(make_tuple(xs, ys) ...) сделает следующее (рис. 8.1).
Сначала элементы из наборов xs и ys будут сгруппированы попарно. Это «попарное чередование» выполняется в вызове make_tuple(xs,ys). Сначала мы получим список кортежей переменной длины по два элемента в каждом. Чтобы получить один большой кортеж, мы применяем вызов tuple_cat — в результате получаем большой сконкатенированный кортеж, содержащий все члены исходных кортежей, которые чередуются.
Замена void* с использованием std::any для повышения безопасности типов
Может случиться так: нам понадобится сохранить элементы любого типа в переменной. Для такой переменной следует проверить, содержит ли она что-либо, и если да, то нужно определить, что именно. Все это надо сделать безопасно для типов.
В прошлом мы имели возможность хранить указатели на различные объекты в указателе типа void*. Такой указатель сам по себе не может сказать, на какой объект ссылается, поэтому нужно вручную создать некий дополнительный механизм, который сообщит, чего стоит ожидать. Данное решение приводит к созданию некрасивого и небезопасного кода.
Еще одним дополнением к STL в C++17 является тип std::any. Он разработан для того, чтобы хранить переменные любого вида, и предоставляет средства, которые позволяют выполнить проверку, безопасную для типов, и получить доступ к данным.
В текущем разделе мы поработаем с этим вспомогательным типом для того, чтобы несколько лучше его понять.
Как это делается
В этом примере мы реализуем функцию, которая пробует вывести на экран какие-либо данные. В качестве ее типа аргумента служит тип std::any.
1. Сначала включим необходимые заголовочные файлы и объявим об использовании пространства имен std:
#include <iostream>
#include <iomanip>
#include <list>
#include <any>
#include <iterator>
using namespace std;
2. Чтобы сократить объем использования угловых скобок в следующей программе, определим псевдоним для типа list<int> и будем применять его впоследствии:
using int_list = list<int>;
3. Реализуем функцию, которая утверждает, что может вывести на экран любые данные. Обещание заключается вот в чем: она выводит любые данные, предоставленные как аргумент в виде переменной std::any:
void print_anything(const std::any &a)
{
4. Первое, что нужно сделать, — проверить, содержит ли аргумент какие-то данные, или же это пустой экземпляр типа any. Если он пуст, то нет смысла пытаться определить, как его выводить на экран.
if (!a.has_value()) {
cout << "Nothing.n";
5. Если он не пуст, то можно попробовать сравнивать его с разными типами до тех пор, пока не получим совпадение. Первым типом послужит тип string. Если это строка, то можно выполнить преобразование a к ссылке string с помощью std::any_cast и просто вывести его на экран. Из соображений эстетики мы поместим строку в кавычки:
} else if (a.type() == typeid(string)) {
cout << "It's a string: "
<< quoted(any_cast<const string&>(a)) << 'n';
6. Если это не string, то может быть int. При совпадении данного типа можно использовать преобразование any_cast<int>, чтобы получить реальное значение int:
} else if (a.type() == typeid(int)) {
cout << "It's an integer: "
<< any_cast<int>(a) << 'n';
7. std::any работает не только для простых типов наподобие string и int. В переменную any можно поместить и ассоциативный массив, список или экземпляр любого другого сложного типа данных. Посмотрим, являются ли входные данные списком целых чисел, и если да, то можем вывести его точно так же, как и любой другой список:
} else if (a.type() == typeid(int_list)) {
const auto &l (any_cast<const int_list&>(a));
cout << "It's a list: ";
copy(begin(l), end(l),
ostream_iterator<int>{cout, ", "});
cout << 'n';
8. Если не подошел ни один из перечисленных типов, то у нас закончатся догадки. В таком случае просто сдадимся и скажем пользователю, что не знаем, как выводить эти данные на экран:
} else {
cout << "Can't handle this item.n";
}
}
9. В функции main можем вызвать эту функцию с произвольными типами, с пустой переменной типа any с помощью {} или передать ей строку "abc" или целое число. Поскольку экземпляр типа std::any может быть создан на основе этих типов неявно, не возникает задержек, связанных с синтаксисом. Мы даже можем создать целый список и передать его в эту функцию:
int main()
{
print_anything({});
print_anything("abc"s);
print_anything(123);
print_anything(int_list{1, 2, 3});
10. Если мы будем помещать объекты, копировать которые действительно дорого, в переменную типа any, то можем также выполнить конструкцию «на месте» (in-place). Попробуем сделать это для нашего списочного типа. Выражение in_place_type_t<int_list>{} представляет собой пустой объект, дающий