Статья Автор: Деникина Н.В., Деникин А.В.

std::tuple

SilverTests · Справочник C++std::tuple
SilverTests.ruСтандартная библиотека · <tuple>
std::tuple

Кортеж произвольной длины из стандартной библиотеки C++


1Что это такое

std::tuple — шаблонный тип, который хранит фиксированное число значений разных типов в одном объекте. Это обобщение std::pair на произвольное число элементов: если pair<A, B> хранит два значения, то tuple<A, B, C, D, ...> — сколько угодно.

Вопрос Ответ
Версия стандарта C++11 (доработки в C++14 и C++17)
Заголовок #include <tuple>
Количество элементов Любое — вариадический шаблон
Типы элементов Разные, фиксируются на этапе компиляции
Доступ по индексу Только через std::get<N>(t), [] не работает
#include <tuple>
#include <string>

std::tuple<int, std::string, double> t{42, "hello", 3.14};
Когда применять. Когда нужно вернуть из функции несколько значений, сгруппировать разнотипные данные без отдельной структуры или задать составной приоритет для priority_queue / ключ для set. Если набор данных осмысленный и часто используется — лучше сделать именованную struct с понятными полями.

2Создание
Прямой конструктор
std::tuple<int, double, std::string> a(1, 2.5, "hi");
std::tuple<int, double, std::string> b{1, 2.5, "hi"};  // uniform init, C++11
std::make_tuple — без указания типов (C++11)
auto t = std::make_tuple(1, 2.5, std::string("hi"));
// тип выведется сам: std::tuple<int, double, std::string>

Функция make_tuple выводит типы из аргументов — это удобнее, чем выписывать их вручную. Один нюанс: make_tuple("hi") даёт tuple<const char*>, а не tuple<std::string>. Если хочется именно строку — оборачивай явно.

CTAD — вывод типов конструктором (C++17)
std::tuple t{1, 2.5, std::string("hi")};  // типы выводятся сами, без make_tuple

С C++17 угловые скобки можно вообще опустить — компилятор выведет параметры шаблона из аргументов. Для короткого литерала "hi" всё равно получится const char*, так что для строк нужна явная обёртка.


3Доступ к элементам

Оператор [] у std::tuple не определён. Причина в том, что тип возвращаемого значения зависит от индекса, поэтому индекс обязан быть известен на этапе компиляции. Синтаксис — шаблонный параметр в угловых скобках:

std::tuple<int, std::string, double> t{42, "hello", 3.14};

int         x = std::get<0>(t);  // 42
std::string y = std::get<1>(t);  // "hello"
double      z = std::get<2>(t);  // 3.14
Доступ по типу (C++14)

Если тип встречается в кортеже ровно один раз, можно спрашивать по нему:

auto s = std::get<std::string>(t);  // "hello"
auto n = std::get<int>(t);          // 42

Если тип встречается несколько раз — ошибка компиляции, надо использовать индекс.

Запись по индексу

std::get возвращает ссылку на элемент, значит через него можно и писать:

std::get<0>(t) = 100;
std::get<1>(t) += " world";
Индекс — только compile-time константа. Нельзя написать std::get<i>(t), где i — обычная переменная. Если нужен доступ по runtime-индексу, то tuple — неподходящая структура: бери std::array, std::vector или std::variant.

4Распаковка на несколько переменных
Structured bindings (C++17) — самый удобный способ
std::tuple<int, std::string, double> t{42, "hello", 3.14};

auto  [x, y, z] = t;         // копии
auto& [a, b, c] = t;         // ссылки — через них можно менять t
const auto& [p, q, r] = t;  // ссылки для чтения

Число имён в скобках должно точно совпадать с числом элементов кортежа — меньше или больше нельзя.

std::tie — распаковка в существующие переменные (C++11)
int x;
std::string y;
double z;

std::tie(x, y, z) = t;

// std::ignore — пропустить ненужное поле
std::tie(x, std::ignore, z) = t;

Исторически это был основной способ до C++17. Сейчас structured bindings почти всегда удобнее, но у std::tie остался важный идиоматический трюк — лексикографическое сравнение нескольких полей:

bool operator<(const Point& a, const Point& b) {
    return std::tie(a.x, a.y, a.z) < std::tie(b.x, b.y, b.z);
}

Без tie пришлось бы вручную расписывать каскад сравнений через if или &&.


5Полезные функции
Функция Версия Что делает
std::make_tuple C++11 Создать кортеж без явного указания типов
std::tie C++11 Создать кортеж из ссылок — для распаковки и сравнения
std::forward_as_tuple C++11 Кортеж forwarding-ссылок — для шаблонной передачи аргументов
std::tuple_cat C++11 Склеить несколько кортежей в один
std::apply C++17 Вызвать функцию, развернув кортеж в её аргументы
std::make_from_tuple C++17 Создать объект, передав кортеж как набор аргументов конструктора
tuple_cat — склейка
auto a = std::make_tuple(1, 'a');
auto b = std::make_tuple("hi", 3.14);
auto c = std::tuple_cat(a, b);  // tuple<int, char, const char*, double>
std::apply — развернуть кортеж в аргументы функции (C++17)
int add3(int x, int y, int z) { return x + y + z; }

auto args = std::make_tuple(1, 2, 3);
int s = std::apply(add3, args);  // 6 — то же, что add3(1, 2, 3)

Очень удобно, когда аргументы заранее собраны в кортеж — например, при передаче через variadic templates или при работе с std::function.


6Мета-информация: размер и типы полей

Всё, что нужно в шаблонном коде для работы с кортежем как с последовательностью типов:

using T = std::tuple<int, std::string, double>;

// Размер кортежа (число элементов):
constexpr std::size_t sz  = std::tuple_size<T>::value;  // 3
constexpr std::size_t sz2 = std::tuple_size_v<T>;       // 3  (C++17)

// Тип элемента по индексу:
using SecondType    = std::tuple_element<1, T>::type;  // std::string
using SecondTypeAlt = std::tuple_element_t<1, T>;      // std::string  (C++14)

Комбинация tuple_size_v + tuple_element_t + std::make_index_sequence — стандартный набор для обхода кортежа в compile-time.


7Сравнение кортежей

Кортежи сравниваются лексикографически, покомпонентно слева направо:

std::tuple<int, int, int> a{1, 2, 3};
std::tuple<int, int, int> b{1, 2, 4};

bool less = a < b;   // true: первые два равны, третий 3 < 4
bool eq   = a == b;  // false

Это ровно то, что нужно для составных ключей в std::set / std::map и для приоритетов в std::priority_queue. Именно поэтому в разборе «Constructing the Array» отрезки клались в кучу как tuple<int, int, int>: сначала длина, потом позиция — и priority_queue автоматически разруливала оба критерия приоритета.


8Классический пример: возврат нескольких значений
#include <tuple>
#include <string>

std::tuple<bool, int, std::string> parseInput(const std::string& s) {
    // ... разбираем строку ...
    return {true, 42, "ok"};  // C++17: фигурные скобки без имени типа
}

int main() {
    auto [success, code, message] = parseInput("...");
    if (success) {
        // используем code и message
    }
}

До C++17 приходилось писать через std::tie с заранее объявленными переменными — получалось многословно:

bool success;
int code;
std::string message;
std::tie(success, code, message) = parseInput("...");
В соревновательном программировании tuple чаще всего возникает в трёх местах: ключ priority_queue / set с составным приоритетом; возврат нескольких значений из вспомогательной функции; рёбра графа вида (вес, u, v) для алгоритма Крускала.

9Типичные ошибки
Индексация обычной переменной
int i = getIndex();
auto x = std::get<i>(t);  // ОШИБКА: i не compile-time константа

Индекс обязан быть известен компилятору. Если нужна динамика — это не tuple.

std::get по типу с дубликатами
std::tuple<int, int, double> t{1, 2, 3.0};
auto x = std::get<int>(t);  // ОШИБКА: int встречается дважды
make_tuple и строковые литералы
auto t = std::make_tuple("hello");  // tuple<const char*>, не std::string!

Если нужна строка — оборачивай явно: std::make_tuple(std::string("hello")) или с литералом "hello"s из C++14 (после using namespace std::string_literals).

Неверное число имён в structured binding
std::tuple<int, int, int> t{1, 2, 3};
auto [a, b] = t;  // ОШИБКА: нужно ровно 3 имени
Забытый std::ignore
// Плохо: объявляем переменную, которая не нужна:
int unused;
std::tie(x, unused, z) = t;

// Хорошо:
std::tie(x, std::ignore, z) = t;

10Краткая шпаргалка
Задача Код Версия
Подключение #include <tuple> C++11
Создать с типами std::tuple<int, std::string> t{1, "hi"}; C++11
Создать без типов auto t = std::make_tuple(1, "hi"); C++11
Создать через CTAD std::tuple t{1, std::string("hi")}; C++17
Доступ по индексу std::get<0>(t)  — индекс только compile-time C++11
Доступ по типу std::get<std::string>(t)  — тип должен быть уникальным C++14
Запись по индексу std::get<0>(t) = 100; C++11
Распаковка в новые переменные auto [a, b, c] = t; C++17
Распаковка в существующие std::tie(a, b, c) = t; C++11
Пропустить поле при tie std::tie(a, std::ignore, c) = t; C++11
Размер кортежа std::tuple_size_v<T> C++17
Тип элемента по индексу std::tuple_element_t<1, T> C++14
Склейка кортежей std::tuple_cat(a, b) C++11
Развернуть в аргументы функции std::apply(func, args); C++17
Сравнение Лексикографическое: a < b, a == b — покомпонентно C++11
Возврат из функции return {true, 42, "ok"}; C++17
Главное. Кортеж — это compile-time структура для разнотипных данных. Индексы — только константы, динамического доступа нет. Основные применения: возврат нескольких значений из функции и составные ключи для set / map / priority_queue. Для частой группы данных лучше именованная struct.
© SilverTests.ru · Справочник C++ · std::tuple
Печать