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.