Выбрать главу

const auto [name, valid_time, price] = stock_info("INTC");

3. Декомпозицию можно применять и для пользовательских структур. В качестве примера создадим следующую структуру.

struct employee {

    unsigned id;

    std::string name;

    std::string role;

    unsigned salary;

};

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

int main()

{

  std::vector<employee> employees {

  /* Инициализируется в другом месте */};

  for (const auto &[id, name, role, salary] : employees) {

    std::cout << "Name: " << name

              << "Role: " << role

              << "Salary: " << salary << '\n';

  }

} 

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

Структурированные привязки всегда применяются по одному шаблону:

auto [var1, var2, ...] = <выражение пары, кортежа, структуры или массива>;

□ Количество переменных var1, var2... должно точно совпадать с количеством переменных в выражении, в отношении которого выполняется присваивание.

□ Элементом <выражение пары, кортежа, структуры или массива> должен быть один из следующих объектов:

  • std::pair;

  • std::tuple;

  • структура. Все члены должны быть нестатическими и определенными в одном базовом классе. Первый объявленный член присваивается первой переменной, второй член — второй переменной и т.д.;

  • массив фиксированного размера.

□ Тип может иметь модификаторы auto, const auto, const auto& и даже auto&&

  При необходимости пользуйтесь ссылками, а не создавайте копии. Это важно не только с точки зрения производительности.

Если в квадратных скобках вы укажете слишком мало или слишком много переменных, то компилятор выдаст ошибку.

std::tuple<int, float, long> tup {1, 2.0, 3};

auto [a, b] = tup; // Не работает

В этом примере мы пытаемся поместить кортеж с тремя переменными всего в две переменные. Компилятор незамедлительно сообщает нам об ошибке:

error: type 'std::tuple<int, float, long>' decomposes into 3 elements,

but only 2 names were provided

auto [a, b] = tup;

Дополнительная информация

С помощью структурированных привязок вы точно так же можете получить доступ к большей части основных структур данных библиотеки STL. Рассмотрим, например, цикл, который выводит все элементы контейнера std::map:

std::map<std::string, size_t> animal_population {

  {"humans", 7000000000},

  {"chickens", 17863376000},

  {"camels", 24246291},

  {"sheep", 1086881528},

  /* … */

};

for (const auto &[species, count] : animal_population) {

  std::cout << "There are " << count << " " << species

            << " on this planet.\n";

}

Пример работает потому, что в момент итерации по контейнеру std::map мы получаем узлы std::pair<const key_type, value_type> на каждом шаге этого процесса. Именно эти узлы распаковываются с помощью структурированных привязок (key_type представляет собой строку с именем species, а value_type — переменную count типа size_t), что позволяет получить к ним доступ по отдельности в теле цикла.

До появления C++17 аналогичного эффекта можно было достичь с помощью std::tie:

int remainder;

std::tie(std::ignore, remainder) = divide_remainder(16, 5);

std::cout << "16 % 5 is " << remainder << '\n';

Здесь показано, как распаковать полученную пару в две переменные. Применение контейнера std::tie не так удобно, как использование декомпозиции, ведь нам надо заранее объявить все переменные, которые мы хотим связать. С другой стороны, пример демонстрирует преимущество std::tie перед структурированными привязками: значение std::ignore играет роль переменной-пустышки. В данном случае частное нас не интересует и мы отбрасываем его, связав с std::ignore.

  Когда мы применяем декомпозицию, у нас нет переменных-пустышек tie, поэтому нужно привязывать все значения к именованным переменным. Это может оказаться неэффективным, если позже не задействовать некоторые переменные, но тем не менее компилятор может оптимизировать неиспользованное связывание.