C++17 STL Стандартная библиотека шаблонов — страница 84 из 119

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 

#include 

#include 

#include 

#include 


using namespace std;


2. Чтобы сократить объем использования угловых скобок в следующей программе, определим псевдоним для типа

list
и будем применять его впоследствии:


using int_list = list;


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(a)) << '\n';


6. Если это не

string
, то может быть
int
. При совпадении данного типа можно использовать преобразование
any_cast
, чтобы получить реальное значение
int
:


  } else if (a.type() == typeid(int)) {

    cout << "It's an integer: "

<< any_cast(a) << '\n';


7. 

std::any
работает не только для простых типов наподобие
string
и
int
. В переменную any можно поместить и ассоциативный массив, список или экземпляр любого другого сложного типа данных. Посмотрим, являются ли входные данные списком целых чисел, и если да, то можем вывести его точно так же, как и любой другой список:


  } else if (a.type() == typeid(int_list)) {

    const auto &l (any_cast(a));

    cout << "It's a list: ";

    copy(begin(l), end(l),

         ostream_iterator{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{}
представляет собой пустой объект, дающий конструктору типа any достаточно информации о том, что мы собираемся создать. Второй параметр,
{1,2,3}
, — просто список инициализации, который будет передан в
int_list
, будучи встроенным в переменную типа
any
с целью создания объекта. Это способ избежать ненужного копирования или перемещения.


  print_anything(any(in_place_type_t{}, {1, 2, 3}));

}


11. Компиляция и запуск программы дадут следующие результаты, они полностью соответствуют нашим ожиданиям:


$ ./any

Nothing.

It's a string: "abc"

It's an integer: 123

It's a list: 1, 2, 3,

It's a list: 1, 2, 3,


Как это работает

Тип

std::any
в чем-то похож на тип
std::optional
— он поддерживает метод
has_value()
, который говорит, содержит ли экземпляр значение. Но, помимо этого, он может содержать все что угодно, вследствие чего с ним работать немного сложнее, нежели с типом