#108 – Crash course on std::pair

Sometimes a function or container needs to keep two related values together.

You could write your own small struct, but there’s no need to reinvent the wheel.

C++ already provides a simple standard option: std::pair. It is a class template from <utility> that stores exactly two values, named first and second.

Three things to take away:

  • std::pair<T, U> stores two values, possibly of different types.
  • The two members are named first and second.
  • Use std::pair for small, obvious two-value groupings; use a custom struct when names matter.

The basic idea

std::pair is a class template.

This means the member types are supplied in angle brackets:

std::pair<int, double>

That pair stores:

first  → int
second → double

Example:

#include <iostream>
#include <utility>

int main(){
    std::pair<int, double> sensorReading { 7, 24.6 };

    std::cout << "Sensor ID: " << sensorReading.first << '\n';
    std::cout << "Temperature: " << sensorReading.second << " C\n";
}

Terminal output:
Sensor ID: 7
Temperature: 24.6 C

The pair contains two members:

sensorReading.first
sensorReading.second

Heterogeneous values

std::pair can store two different types.

#include <iostream>
#include <string>
#include <utility>

int main(){
    std::pair<std::string, int> playerScore { "Ada", 900 };

    std::cout << playerScore.first << " scored "
              << playerScore.second << " points\n";
}

Terminal output:
Ada scored 900 points

Here, the first value is a std::string.

The second value is an int.

That is why std::pair is called a small heterogeneous container: it can hold two values of different types.

Class template argument deduction (CTAD) C++17

CTAD is compatible with std::pair.

#include <iostream>
#include <string>
#include <utility>

int main(){
    std::pair playerScore { std::string { "Ada" }, 900 };

    std::cout << playerScore.first << " scored "
              << playerScore.second << " points\n";
}

Terminal output:
Ada scored 900 points

The compiler deduces:

std::pair<std::string, int>

Returning two related values

std::pair is useful when a function naturally returns two values and the relationship is obvious.

For example, splitting a score into wins and losses:

#include <iostream>
#include <utility>

std::pair<int, int> seasonRecord(){
    return { 14, 6 };
}

int main(){
    std::pair<int, int> record { seasonRecord() };

    std::cout << "Wins: " << record.first << '\n';
    std::cout << "Losses: " << record.second << '\n';
}

Terminal output:
Wins: 14
Losses: 6

This is compact. However, once the values are stored in the members, the semantics are lost

record.first // What does this mean?
record.second

std::pair in the Standard Library

std::pair appears throughout the Standard Library.

A major example is std::map.

Each element in a std::map<K, V> behaves like a pair:

std::pair<const K, V>

The first member is the key.

The second member is the mapped value.

#include <iostream>
#include <map>
#include <string>

int main(){
    std::map<std::string, int> stock {
        { "resistors", 250 },
        { "capacitors", 120 }
    };

    for (const auto& item : stock) {
        std::cout << item.first << ": "
                  << item.second << '\n';
    }
}

// Terminal output:
capacitors: 120
resistors: 250

The map stores key-value pairs.

That is why this syntax appears:

item.first
item.second

first is the key.

second is the value.

Comparing pairs

std::pair supports comparison if its stored types support comparison.

Pairs are compared lexicographically: first compare first, then compare second if needed.

#include <iostream>
#include <utility>

int main(){
    std::pair<int, int> a { 2, 5 };
    std::pair<int, int> b { 2, 9 };

    std::cout << std::boolalpha;
    std::cout << (a < b) << '\n';
    std::cout << (a == b) << '\n';
}

Terminal output:
true
false

The first members are equal:

2 == 2

So the comparison moves to the second members:

5 < 9

Therefore, a < b is true.

When not to use std::pair

std::pair is convenient, but it can become unclear.

This is acceptable for small, obvious groupings:

std::pair<int, int> record { 14, 6 };

But if the meaning matters across the program, prefer a named struct:

struct SeasonRecord {
    int wins {};
    int losses {};
};

Now the call site is self-documenting:

record.wins
record.losses

That is clearer than:

record.first
record.second

The rule is simple:

Use std::pair when the relationship is obvious and local. Use a struct when the values deserve names.

std::pair vs std::tuple

std::pair stores exactly two values.

std::tuple generalizes this idea to zero or more values:

std::tuple<int, double, char>

Use std::pair when there are exactly two values and first / second is readable enough.

Use a custom struct when member names matter.

Use std::tuple sparingly when the values are positional and the grouping is small. If a tuple starts needing explanation, it probably wants to become a struct.

Takeaway

std::pair is a small, simple standard class template for storing exactly two related values:

It is useful for local two-value results, key-value relationships, and Standard Library interfaces such as std::map. However, the trade-off is a loss in semantics.