§9 Вариант, пара и кортеж

std::variant (Вариант)

Здесь мы рассмотрим элементы языка, которые, на первый взгляд, могут показаться излишними. Однако, в процессе составления более сложных программ эти утилиты весьма будут кстати и помогут создать более упорядоченный, понятный и изящный код. Эти элементы языка буду применяться далее в программах базового и расширенного курса по программированию на С++. Вариант, пара и кортеж являются составной частью библиотеки утилит STD С++ (Utility library).
Все три утилиты являются шаблонными классами. О шаблонах и классах мы будем говорить позднее. Это накладывает некоторые особенности на создаваемый объект в программе, по сравнению с описанием переменных фундаментальных типов, рассмотренных нами ранее.
Шаблонный класс std::variant представляет собой типобезопасное объединение (union; в данном курсе стандартный union не рассматривается). Объект std::variant в любой момент времени содержит значение одного из альтернативных типов представленных в этом объединении. Иными словами, вариант представляет собой переменную, которая запоминает значение типа, которое он хранит в данный момент из множества допустимых.
Вариант не может содержать ссылки, С-массивы или тип void, но может содержать объект класса контейнера. Вариант может включать один и тот же тип более одного раза и содержать версии одного и того же типа, но с различной квалификацией. Определяется вариант следующим образом:

std::variant<Type> name;

Вариант можно инициализировать начальным значением так:

variant<T> name();
variant<T> name(const variant& other); // копирование
variant<T> name(variant&& other);      // перемещение
variant<T> name(T&& t);                // конвертирование
// альтернативные аргументы по заданной позиции i
variant<T> name(in_place_index<i>, args); 
// альтернативные аргументы по типу
variant<T> name(in_place_type<Type>, args); 

С шаблонами мы сталкиваемся впервые. Подробные сведения о шаблонах мы дадим позднее. Здесь же обратите внимание на то, что объект шаблонного класса (в типе) или шаблонные функции (в имени) имеют дополнительный шаблонный параметр, заключенный в угловые скобки (<>), с помощью которого передаются сведения о типах параметров используемых в классе или функции.

Операции и функции
  • operator= – присваивание
  • index – возвращает индекс содержащийся в варианте
  • valueless_by_exception – возвращает false тогда, когда вариант содержит значение
  • emplace – устанавливает значение в варианте на месте
  • swap – обмен с другим вариантом
  • visit – вызывает предоставленный функтор с аргументами одного или нескольких вариантов
  • holds_alternative – проверяет, содержит ли вариант данный тип
  • get – читает значение варианта с учетом индекса или типа
  • get_if – получает указатель на значение указываемого варианта по индексу или типу (если уникален), возвращает ноль при ошибке
  • operator<, operator<=, operator>, operator>=, operator==, operator!= - операции сравнения
Обратите внимание на то, что для присваивания нового значения варианту можно использовать перегруженную операцию "=". Также с вариантами можно использовать все существующие операции сравнения.
Для добавления элемента в вариант необходимо использовать метод emplace:

var.emplace<T>(value);

Для доступа к значению варианта применяется функция std::get<T>(). В качестве шаблонного параметра выступает либо тип, либо индекс. Однако с помощью get() можно получить доступ только к тем данным, которые сохранены в настоящий момент и, если произошла ошибка (т.е. выбран не тот тип или не тот индекс), то будет сгенерировано исключение. Чтобы узнать активный индекс нужно использовать метод index(). А для того, чтобы получить логическое значение проверки соответствия варианта определенному типу, нужно использовать функцию holds_alternative<T>(), в шаблонном параметре которой указывается проверяемый тип. Пример работы этих функций представлен ниже.

Программа cpp-9.1
#include <iostream>
#include <variant>
#include <string>
using namespace std;

int main() {
    variant<float, long, double> S {2.3f};
    variant<double> S2 {in_place_index<0>, 3. };
    variant<string, const char *> S3(in_place_type<string>, "Hello World");
    cout << get<float>(S) << endl;
    cout << get<0>(S2) << endl;
    cout << get<string>(S3) << endl;
    S.emplace<long>(12345);
    S.emplace<2>(1.2);
    cout << get<double>(S) << endl;
    S = 4.6f;
    cout << S.index() << endl;
    S = 54213465l;
    if (holds_alternative<long>(S))
        cout << S.index() << endl;
    return 0;
}

Вывод

2.3
3
Hello World
1.2
0
1

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

Программа cpp-9.2
#include <iostream>
#include <variant>
#include <string>
using namespace std;

int main() {
  int ID;
  string status;
  variant<string, string, string, int> id;
  cout << "Введите свой ID или 0, если"
          "\nвы не являетесь членом клуба:" << endl;
  cin >> ID;
  cout << "Введите свой статус: vip, use или non:" << endl;
  cin >> status;
  if (status == "vip") id.emplace<0>(status);
  if (status == "use") id.emplace<1>(status);
  if (status == "non") id.emplace<2>(status);
  if (!ID) id.emplace<3>(ID); // Аутентификация
  switch (id.index ()) {
    case 0: cout << "Приветствуем Вас, vip-" << ID << endl; break;
    case 1: cout << "Вы вошли как: " << ID << endl; break;
    case 2: cout << "Ваш ID не имеет привилегий!" << endl; break;
    case 3: cout << "Ваш статус в клубе не подтвержден!" << endl; break;
    }
  return 0;
}

Вывод

Введите свой ID или 0, если
вы не являетесь членом клуба:
451367889
Введите свой статус: vip, use или non:
vip
Приветствуем Вас, vip-451367889

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

Для просмотра возможных значений варианта удобно использовать функцию visit(). Но знакомство с ней мы перенесем на более позднее время, когда у вас появятся знания о лямбда-выражениях, шаблонах, классах и структурах.

std::pair (Пара)

std::pair - это класс, который предоставляет возможность хранить два разнородных объекта, как единое целое. pair используется в разных местах STD, в частности, в алгоритме minmax(), методе класса set equal_range(), для работы с элементами ассоциативных контейнеров, которые также представляют из себя пары: Ключ: Значение. Например, с помощью пары удобно описывать положение объекта на плоскости (принимать или возвращать координаты x и y в функциях).
Для создания объекта пары используется следующий синтаксис:

pair<T1, T2> p1;
pair<T1, T2> p2(val1, val2);
pair<T1, T2> p3(p1);
Операции и функции
  • operator= – присваивание (с возможностью неявного преобразования типов)
  • p.first – доступ к первому значению пары (по ссылке)
  • p.second – доступ ко второму значению пары (по ссылке)
  • p->first – доступ к первому значению пары (по указателю)
  • p->second – доступ ко второму значению пары (по указателю)
  • p.swap(other_p) или swap(p, other_p) – обмен значениями между p и other_p
  • operator<, operator<=, operator>, operator>=, operator==, operator!= - операции сравнения
  • make_pair(val1, val2) - возвращает пару
В программе ниже демонстрируется несколько способов получения элементов пары по ссылке и указателю.

Программа cpp-9.3
#include <iostream>
#include <utility>
using namespace std;

int main() {
    int a1 = 1, b1 = 2;
    int a2 = 3, b2 = 4;
    pair<int, int> par1(a1, b1);
    pair<int, int> *par2 = new pair<int, int>(a2, b2);
    cout << "Первый элемент" << '\t' << "Второй элмент"
            "\nПервая пара\n"
         << par1.first << "\t\t" << par1.second
         << "\nВторая пара\n"
         << par2->first << "\t\t" << par2->second
         << "\nПервая пара\n"
         << get<0>(par1) << "\t\t" << get<1>(par1)   // utility
         << "\nВторая пара\n"
         << get<0>(*par2) << "\t\t" << get<1>(*par2) // utility
         << endl;
    return 0;
}

Вывод

Первый элемент  Второй элмент
Первая пара
1               2
Вторая пара
3               4
Первая пара
1               2
Вторая пара
3               4
Современный синтаксис разрешает не использовать в определении шаблонные параметры, если производится инициализация пары (или кортежа). В строке 8 и 9 можно было бы записать следующие инструкции:

pair par1(a1, b1);
pair<int, int> *par2 = new pair (a2, b2);

Присваивать значение паре можно с помощью простого синтаксиса, например:

pair<int, int> p3;
p3 = {2, 4};

std::tuple (Кортеж)

Шаблонный класс tuple представляет собой набор разнородных значений фиксированного размера (два и белее). Он является обобщением std::pair, поэтому функции и операции для пары и кортежа схожи.
Также как и pair, кортеж удобно применять если из функции нужно принять или ввести несколько аргументов в виде единого объекта. Пара и кортеж могут быть элементами массивов (как элементы абстрактного типа). Доступ к элементам кортежа осуществляется с помощью функции get(), в шаблонном параметре которой указывается индекс.

Программа cpp-9.4
#include <iostream>
#include <tuple>
using namespace std;

int main() {
    tuple<string, int, double> t1;
    tuple<string, int, double> t2;
    get<0>(t1) = "Ivanov";
    get<1>(t1) = 123456;
    get<2>(t1) = 4.2;
    get<0>(t2) = "Petrov";
    get<1>(t2) = 654321;
    get<2>(t2) = 4.8;
    if (get<2>(t2) > get<2>(t1))
        cout << get<0>(t2) << '\t'
             << get<1>(t2) << '\t'
             << get<2>(t2) << endl;
    t2 = t1;
    t1 = {"Sidorova", 987456, 4.5};
    cout << get<0>(t1) << '\t'
         << get<1>(t1) << '\t'
         << get<2>(t1) << endl;
    
    return 0;
}

Вывод

Petrov          654321  4.8
Sidorova        987456  4.5
Обратите внимание, что для работы с tuple (в отличие от пары) необходимо использовать одноименный заголовок.

Следует отличать кортеж от массива. Например, в кортеже не используется операция [] и обойти кортеж в цикле, как массив, не удасться.

Если необходимо создать кортеж с большим количеством однотипных элементов, то следует использовать шаблонный класс массива фиксированного размера array, с которым мы вскоре познакомимся.
Функция tie(), заполнитель ignore и декомпозиция

С помощью функции tie() можно организовать кортеж ссылок. Объект ignore применяется с tie при распаковке кортежа как заполнитель аргументов, которые не используются.

Программа cpp-9.5.1
#include <iostream>
#include <string>
#include <tuple>
using namespace std;

int main() {
    tuple<string, int, double> t1;
    t1 = {"Sidorova", 987456, 4.5};
    string S;
    double d;
    tie(S, ignore, d) = t1;
    cout << S << '\n'
         << d << endl;
    return 0;
}

Вывод

Sidorova
4.5

В этой программе аргументы функции tie (S и d) являются ссылками на переменные, которые будут инициализированы соответствующими значениями кортежа с индексами 0 и 2. Первый индекс будет проигнорирован.

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

tuple t1 = {"Sidorova", 987456, 4.5};

Заменой функции tie, как для пары, так и для кортежа, может выступать декомпозиция, подробнее о которой мы будем говорить позднее. Заметим, что, во-первых, применять std::ignore, в этом случае, нельзя, во-вторых, количество используемых переменных должно совпадать с количеством элементов в кортеже. Тем не менее, код становится более компактным за счет того, что не нужно заранее объявлять эти переменные:

Программа cpp-9.5.2
int main() {
    tuple t1 {"Sidorova", 987456, 4.5};
    auto [S, non, d] = t1; // переменная non не используется!
    cout << S << '\n'
         << d << endl;
    return 0;
}
Примеры решения задач
Вопросы
Темы сообщений
Задания А
Задания Б
Задания С
Ссылки
1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (2 оценок, среднее: 5,00 из 5)
Загрузка...

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Print Friendly, PDF & Email

Обсуждение закрыто.