Skip to content
Theory · Containers and Iterators · lesson 3 of 11

map, set and unordered_map

Finding a product's price by name in a vector means comparing the name with every element in turn. std::map stores key-value pairs and finds a value by its key without such a scan:

C++
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stock;
    stock["pear"] = 4;
    stock["apple"] = 7;
    stock["pear"] = 2;
    std::cout << stock.size() << ' ' << stock["pear"] << '\n';  // 2 2
    for (const auto& item : stock) {
        std::cout << item.first << '=' << item.second << ' ';  // apple=7 pear=2
    }
}

In std::map<std::string, int>, the first type is the type of the key, and the second is the type of the value. The line stock["pear"] = 4 puts a pair with the key "pear" into the container. Keys do not repeat: the second assignment to the key "pear" replaced the value, so there are still two elements.

The elements of a std::map are ordered by key, so apple is printed before pear, even though it was added later. Each element is a pair: item.first is the key, and item.second is the value. The variable item is declared as const auto&: the elements are not copied, and the compiler deduces the type.

Lookup without inserting

The [] operator returns the value for a key, and if the key is missing, it adds the key first. The new value is initialized like int x{}: 0 for an int, an empty string for a std::string. So even reading a missing key increases the size. To check for a key without adding anything, use the contains (C++20) and count functions:

C++
std::map<std::string, int> stock{{"apple", 7}};
std::cout << stock.contains("kiwi") << ' ' << stock.count("apple") << '\n';  // 0 1
std::cout << stock["kiwi"] << '\n';  // 0
std::cout << stock.size() << '\n';  // 2

Initial pairs are written in curly braces: {{"apple", 7}}. The count function returns the number of elements with the given key, which for a std::map is 0 or 1.

set and unordered_map

std::set from the <set> header stores only keys, with no values. Its keys are unique and ordered, and inserting the same key again with insert changes nothing:

C++
std::set<int> seen;
seen.insert(3);
seen.insert(1);
seen.insert(3);
std::cout << seen.size() << ' ' << seen.contains(1) << '\n';  // 2 1

std::unordered_map from the <unordered_map> header is built as a hash table: from a key, it computes where the element lies. On average, lookup in it is faster than in a std::map, and the functions are the same: [], contains, count. But its keys are not ordered, and the standard does not define the order of traversal.

Common mistake

A function takes a std::map by const reference and reads a value with []:

C++
#include <map>
#include <string>

int price(const std::map<std::string, int>& stock) {
    return stock["apple"];  // ✗
}
TEXT
error: no viable overloaded operator[] for type 'const std::map<std::string, int>' (aka 'const map<basic_string<char>, int>')

The [] operator may add a key, which changes the container, so a const std::map does not have it. Inside such a function, check the key with contains and read the value with at: stock.at("apple") adds nothing. For a missing key, at stops the program with an error, so the check comes first.