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

std::pair

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 — итератор, secondbool (вставилось или уже было)
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 — это итератор, secondbool. Интуитивно хочется наоборот, отсюда частые баги.


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.
© SilverTests.ru · Справочник C++ · std::pair
Печать