Menu

C++ set: Unique, Sorted Elements with std::set

How std::set stores unique, automatically sorted values in C++ - inserting, checking membership with count and find, iterating in order, and the differences between set, multiset, and unordered_set.

This page includes runnable editors - edit, run, and see output instantly.

A Bag of Unique, Sorted Values

You've seen unordered_map store key-value pairs with fast hash-based lookup. A std::set is the simpler cousin: it stores just values - no associated data - and it enforces two rules automatically. Every element is unique (duplicates are silently dropped), and the elements are always kept in sorted order.

That makes set the natural choice for questions like "have I seen this before?" or "give me the distinct items in sorted order." You insert without worrying about duplicates, and you iterate without sorting first.

Notice we inserted 10 twice and out of order, yet the output is sorted and 10 appears once. The set did the bookkeeping for you.

Inserting and Removing

insert adds a value if it isn't already there. It returns a pair whose .second is a bool telling you whether the insert actually happened - handy when you want to know if a value was new:

To remove a value, call erase with the value itself - it returns the number of elements removed (0 or 1 for a set). Erasing something that isn't there is harmless, not an error:

Checking Membership

The whole point of a set is fast "is this in here?" tests. The clearest way is count, which returns 1 or 0:

Since C++20 there's an even more readable option, contains, which returns a bool directly:

if (primes.contains(7)) { /* ... */ }   // C++20

A common mistake is reaching for operator[] the way you would with a map. A set has no operator[] - there's no value to fetch, only presence to test. Use count or contains, not s[7].

If you need the actual position (to erase it, or to look at neighbors), use find, which returns an iterator or end():

Ordered Iteration and Range Queries

Because a set is sorted, iterating always yields elements smallest-to-largest, and you get ordered-container tricks for free. lower_bound(x) gives the first element not less than x, and upper_bound(x) the first element strictly greater than x - together they let you scan a numeric range without checking every element:

A subtle but important rule: set elements are immutable. The iterator hands you a const reference, so you can't change an element in place - doing so could break the sort order the container relies on. To "change" a value, erase the old one and insert the new one.

By default the ordering is ascending (std::less). For descending order, supply a different comparator as the second template argument:

set vs multiset vs unordered_set

std::set is one of three close relatives, and picking the right one matters:

set<int>            // unique values, sorted, O(log n)
multiset<int>       // allows duplicates, sorted, O(log n)
unordered_set<int>  // unique values, NO order, average O(1)

Reach for unordered_set when you only need membership tests and don't care about order - its hash-based lookups are faster on average than the set's tree-based O(log n). Choose set when you need elements in sorted order, range queries with lower_bound/upper_bound, or stable iterator behavior. Use multiset only when duplicates are meaningful (for example, a histogram of repeated values) - in a multiset, count(x) can return more than 1, and erase(x) removes all copies unless you erase by a single iterator.

One classic use of set: deduplicate and sort a vector in one move.

Constructing the set from the vector's iterators drops every duplicate and sorts the rest - no manual loop, no explicit sorting plus std::unique dance.

Next: pair and tuple

You've now seen .first and .second show up on the pair that insert returns, and structured bindings paired with the auto keyword (auto [it, inserted]) unpack it cleanly. Those lightweight "bundle a few values together" types are everywhere in the STL. Next we'll look at pair and tuple directly - how to build them, unpack them, and return multiple values from a function without defining a whole struct.

Frequently Asked Questions

What is a set in C++?

A std::set is an associative container that stores unique values in sorted order. Inserting a value that's already present does nothing, and iterating walks the elements from smallest to largest. Lookups, inserts, and erases are all O(log n) because it's backed by a balanced binary search tree.

How do I check if an element exists in a C++ set?

Use s.count(x) which returns 1 if x is present and 0 if not, or s.contains(x) in C++20 which returns a bool. Avoid s.find(x) != s.end() unless you actually need the iterator - it's the same cost but more verbose.

What is the difference between set and unordered_set in C++?

std::set keeps elements sorted and offers O(log n) operations; std::unordered_set keeps them in no particular order using a hash table for average O(1) operations. Use set when you need ordered iteration or range queries, and unordered_set when you only need fast membership tests and don't care about order.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED