SilverTests · Справочник C++std::pair
SilverTests.ruСтандартная библиотека · <utility>
std::pair
Пара значений разных типов — базовый кирпичик стандартной библиотеки
1Что это такое
std::pair — шаблонный тип, который хранит ровно два значения возможно разных типов. Это частный, самый простой случай кортежа. Появился ещё в C++98 и встречается в стандартной библиотеке буквально повсюду — std::map, std::set::insert, std::minmax и десятки других мест возвращают именно pair.
| Вопрос |
Ответ |
| Версия стандарта |
C++98 (расширения в C++11, C++14, C++17) |
| Заголовок |
#include <utility> |
| Количество элементов |
Всегда 2 |
| Типы элементов |
Разные, фиксируются на этапе компиляции |
| Доступ |
Публичные поля .first и .second |
#include <utility>
#include <string>
std::pair<int, std::string> p{42, "hello"};
std::cout << p.first << " " << p.second; // 42 hello
Когда применять. Когда нужна именно пара «ключ–значение», «координата x, y», «позиция и признак» или любой другой случай ровно из двух значений. Если элементов больше — бери std::tuple. Если имена first и second сильно ухудшают читаемость — делай именованную struct.
2Создание
Прямой конструктор
std::pair<int, std::string> a(1, "hi");
std::pair<int, std::string> b{1, "hi"}; // uniform init, C++11
std::pair<int, std::string> c; // {0, ""} — default-конструкторы обоих типов
std::make_pair — без указания типов (C++98)
auto p = std::make_pair(1, std::string("hi"));
// тип выведется сам: std::pair<int, std::string>
Классика, существует со времён C++98. Один нюанс, общий с make_tuple: make_pair(1, "hi") даст pair<int, const char*>, а не pair<int, std::string>. Для строки оборачивай явно.
CTAD — вывод типов конструктором (C++17)
std::pair p{1, std::string("hi")}; // типы выводятся сами, без make_pair
С C++17 угловые скобки можно опустить. После CTAD функция make_pair во многом потеряла смысл, но в шаблонном коде всё ещё бывает полезна.
Инициализация через {} в контексте
std::map<int, std::string> m;
m.insert({1, "one"}); // pair создаётся из списка инициализации
std::pair<int, int> coord = {3, 5};
3Доступ к элементам
У pair есть публичные поля — это самый удобный способ:
std::pair<int, std::string> p{42, "hello"};
int x = p.first; // 42
std::string y = p.second; // "hello"
p.first = 100; // запись через поле
p.second += " world";
std::get
По индексу (C++11):
int x = std::get<0>(p); // то же, что p.first
std::string y = std::get<1>(p); // то же, что p.second
По типу (C++14), если типы разные:
auto n = std::get<int>(p); // 42
auto s = std::get<std::string>(p); // "hello"
В обычном коде почти всегда пишут p.first / p.second — короче и читаемее. std::get на паре используют в шаблонном коде, где одинаково обрабатываются и pair, и tuple.
4Распаковка на две переменные
Structured bindings (C++17)
std::pair<int, std::string> p{42, "hello"};
auto [x, y] = p; // копии
auto& [a, b] = p; // ссылки, через них можно менять p
const auto& [k, v] = p; // ссылки для чтения
Особенно часто встречается в циклах по std::map:
std::map<std::string, int> scores;
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << '\n';
}
До C++17 приходилось писать неудобное it->first / it->second или kv.first / kv.second внутри тела цикла.
std::tie — распаковка в существующие переменные (C++11)
int x;
std::string y;
std::tie(x, y) = p;
std::tie(x, std::ignore) = p; // игнорируем вторую компоненту
Удобный идиоматический способ задать сравнение нескольких полей структуры:
bool operator<(const Point& a, const Point& b) {
return std::tie(a.x, a.y) < std::tie(b.x, b.y);
}
5Сравнение
Пары сравниваются лексикографически: сначала по first, при равенстве — по second.
std::pair<int, int> a{1, 5};
std::pair<int, int> b{1, 7};
std::pair<int, int> c{2, 0};
bool r1 = a < b; // true: first равны, 5 < 7
bool r2 = b < c; // true: 1 < 2, second уже неважно
bool r3 = a == b; // false
Именно это поведение делает pair удобным ключом для std::map и std::set и элементом для std::priority_queue.
// Расстояние + вершина для алгоритма Дейкстры:
std::priority_queue<std::pair<int, int>,
std::vector<std::pair<int, int>>,
std::greater<>> pq;
pq.push({0, start}); // «стоимость 0 дойти до start»
6Где встречается в стандартной библиотеке
Многие функции стандартной библиотеки возвращают pair — полезно узнавать их в лицо.
| Выражение |
Что в паре |
*map.begin() |
first — ключ, second — значение |
set.insert(x) / map.insert(...) |
first — итератор, second — bool (вставилось или уже было) |
map.equal_range(k) |
Два итератора — начало и конец диапазона ключа |
std::minmax(a, b) |
first — меньшее, second — большее |
std::minmax_element(v.begin(), v.end()) |
Два итератора — на минимум и максимум в диапазоне |
std::mismatch(a, b, c) |
Два итератора на первую несовпавшую позицию в двух диапазонах |
Пример, который возникает постоянно — проверка, что элемент вставился в set:
std::set<int> s;
auto [it, inserted] = s.insert(42);
if (inserted) {
// только что добавили
} else {
// уже был — it указывает на существующий элемент
}
7
Типичные ошибки
Забыли про const в ключе map
std::map<int, std::string> m;
for (auto& [k, v] : m) {
k = 100; // ОШИБКА: k — это const int&
v = "x"; // OK
}
Внутри map<K, V> хранится pair<const K, V> — именно const K, иначе нарушилась бы упорядоченность. Менять ключ через ссылку нельзя.
make_pair и строковые литералы
auto p = std::make_pair(1, "hello"); // pair<int, const char*>
Если нужен std::string — пиши std::make_pair(1, std::string("hello")) или std::pair<int, std::string>{1, "hello"}.
Попытка модифицировать через копию
std::map<int, int> m{{1, 10}};
for (auto [k, v] : m) { // без & — это копии!
v = 999; // меняем копию, в m ничего не изменилось
}
Для модификации нужно auto& (или auto&&).
Перепутан порядок first и second
Особенно в возврате insert: first — это итератор, second — bool. Интуитивно хочется наоборот, отсюда частые баги.
9Краткая шпаргалка
| Задача |
Код |
Версия |
| Подключение |
#include <utility> |
C++98 |
| Создать с типами |
std::pair<int, std::string> p{1, "hi"}; |
C++11 |
| Создать без типов |
auto p = std::make_pair(1, "hi"); |
C++98 |
| Создать через CTAD |
std::pair p{1, std::string("hi")}; |
C++17 |
| Доступ к первому |
p.first |
C++98 |
| Доступ ко второму |
p.second |
C++98 |
| Доступ по индексу |
std::get<0>(p) / std::get<1>(p) |
C++11 |
| Доступ по типу |
std::get<std::string>(p) — типы должны отличаться |
C++14 |
| Распаковка в новые переменные |
auto [a, b] = p; |
C++17 |
| Распаковка в существующие |
std::tie(a, b) = p; |
C++11 |
| Пропустить поле при tie |
std::tie(a, std::ignore) = p; |
C++11 |
| Сравнение |
Лексикографическое: сначала first, потом second |
C++98 |
| Обмен значениями |
p1.swap(p2); или std::swap(p1, p2); |
C++98 |
| Итерация по map |
for (const auto& [k, v] : m) |
C++17 |
Результат insert |
auto [it, ok] = s.insert(x); |
C++17 |
| Возврат из функции |
return {42, "ok"}; |
C++17 |
Главное. std::pair — это кортеж с двумя значениями.first и .second. Лежит в <utility>, сравнивается лексикографически, встречается в возвратах map, set::insert, minmax и многих других. Если имена first/second запутывают смысл — делай именованную struct.