Lexicographical Compare
std::lexicographical_compare and its C++20 siblings: dictionary-order comparison of two ranges, custom comparators, parallel overloads, and three-way results.
std::lexicographical_comparesince C++98Returns true if the first range orders strictly before the second in dictionary order, meaning the first mismatching element pair decides the result, or the first range is a proper prefix of the second.
Overview
std::lexicographical_compare (header <algorithm>) compares two ranges element by element, the way a dictionary orders words. It walks both ranges in lockstep and stops at the first position where the elements are not equivalent.
- If
a[i] < b[i]at that position, the result istrue. - If
b[i] < a[i], the result isfalse. - If one range runs out first, the shorter range is less. An empty range is therefore less than any non-empty range.
- If both run out together, the ranges are equivalent and the result is
false.
The algorithm is a strict less-than. It does not require sorted input, and it never looks at more than min(N1, N2) element pairs. It performs at most 2·min(N1, N2) comparisons, because each step may test both directions.
This is the algorithm behind operator< for std::vector, std::array, std::deque, std::list, std::pair and std::tuple. Before C++20 the relational operators on standard containers were defined in terms of it. Since C++20, containers provide operator<=>, which is defined through lexicographical_compare_three_way.
| Facility | Since | Notes |
|---|---|---|
std::lexicographical_compare (iterators) | C++98 | Default operator< or custom comparator |
| Execution-policy overloads | C++17 | Parallel/vectorised; requires forward iterators |
constexpr on all overloads | C++20 | Usable in constant expressions |
std::ranges::lexicographical_compare | C++20 | Range arguments, projections, constrained |
std::lexicographical_compare_three_way | C++20 | Returns a comparison category, not bool |
Syntax
#include <algorithm>
// C++98
template<class It1, class It2>
bool lexicographical_compare(It1 first1, It1 last1, It2 first2, It2 last2);
template<class It1, class It2, class Compare>
bool lexicographical_compare(It1 first1, It1 last1, It2 first2, It2 last2, Compare comp);
// C++17: ExecutionPolicy overloads of both forms above
template<class ExecutionPolicy, class It1, class It2>
bool lexicographical_compare(ExecutionPolicy&& policy,
It1 first1, It1 last1, It2 first2, It2 last2);
// C++20: ranges version (std::ranges namespace, projections supported)
// std::ranges::lexicographical_compare(r1, r2, comp = {}, proj1 = {}, proj2 = {});
// C++20: three-way version, returns std::strong_ordering / weak_ordering / partial_ordering
// std::lexicographical_compare_three_way(f1, l1, f2, l2, comp = std::compare_three_way{});The two ranges may have different iterator and element types, provided comp(*it1, *it2) and comp(*it2, *it1) are both valid. comp must induce a strict weak ordering.
Examples
Basic comparison
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> a{1, 2, 3};
std::vector<int> b{1, 2, 4};
std::vector<int> c{1, 2};
auto lt = [](const auto& x, const auto& y) {
return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end());
};
std::cout << lt(a, b) << '\n'; // 1: 3 < 4 at index 2
std::cout << lt(c, a) << '\n'; // 1: c is a proper prefix of a
std::cout << lt(a, a) << '\n'; // 0: equivalent, strict ordering
}Case-insensitive ordering
When using <cctype> functions, convert through unsigned char first. Passing a negative char to std::tolower is undefined behaviour.
#include <algorithm>
#include <cctype>
#include <set>
#include <string>
struct CaseInsensitiveLess {
bool operator()(const std::string& a, const std::string& b) const {
return std::lexicographical_compare(
a.begin(), a.end(), b.begin(), b.end(),
[](unsigned char x, unsigned char y) {
return std::tolower(x) < std::tolower(y);
});
}
};
std::set<std::string, CaseInsensitiveLess> names; // "Alice" and "alice" are the same keyMulti-field keys with std::tie
For structured data you usually do not need the algorithm directly. std::tie builds a tuple of references, and tuple comparison is lexicographic.
#include <string>
#include <tuple>
struct Person {
bool vip;
std::string last, first;
friend bool operator<(const Person& l, const Person& r) {
return std::tie(l.vip, l.last, l.first) < std::tie(r.vip, r.last, r.first);
}
};
// C++20: prefer `auto operator<=>(const Person&) const = default;`Ranges version with projections (C++20)
#include <algorithm>
#include <string>
#include <vector>
struct Version { std::vector<int> parts; std::string tag; };
bool older(const Version& a, const Version& b) { // C++20
return std::ranges::lexicographical_compare(a.parts, b.parts);
}
bool older_by_tag(const std::vector<Version>& x, const std::vector<Version>& y) { // C++20
return std::ranges::lexicographical_compare(
x, y, std::less{}, &Version::tag, &Version::tag); // projections on both ranges
}Three-way result (C++20)
When you need less, equal, and greater in a single pass, lexicographical_compare_three_way avoids calling the algorithm twice.
#include <algorithm>
#include <compare>
#include <vector>
std::vector<double> u{1.0, 2.0}, v{1.0, 2.5};
auto r = std::lexicographical_compare_three_way( // C++20
u.begin(), u.end(), v.begin(), v.end());
// r is std::partial_ordering here, because double compares partially
if (r < 0) { /* u before v */ }The result type is deduced from the comparator. With the default std::compare_three_way it is the common comparison category of the element types, so int gives strong_ordering and double gives partial_ordering.
Parallel overload (C++17)
#include <algorithm>
#include <execution>
#include <vector>
bool before(const std::vector<std::uint8_t>& a, const std::vector<std::uint8_t>& b) {
return std::lexicographical_compare(std::execution::par_unseq, // C++17
a.begin(), a.end(), b.begin(), b.end());
}Parallel execution only pays off for very large ranges with cheap comparison. Short-circuiting is harder to exploit in parallel, so profile before adopting it. Some implementations also need an extra library, such as TBB for libstdc++.
Best Practices
- Prefer container operators for container comparison.
a < bon two vectors of the same type is clearer and does the same work. - Use the algorithm when types differ. Examples are comparing a
std::list<int>with astd::vector<int>, or a subrange with a whole container. - Use
std::string_viewfor text (C++17) and compare withcompare()or<. They usechar_traits, which forcharcompares asunsigned char. - Prefer
std::ranges::in C++20 code. Projections remove most hand-written comparator lambdas, and constraints give better diagnostics. - Use the three-way form when you need both
<and==results. One pass replaceslexicographical_comparecalled twice, or a call combined withstd::equal. - Keep comparators stateless and pure. They are called in both argument orders, so they must be consistent.
Common Pitfalls
- Result is
bool, not an ordering.falsemeans "not less", which could be equal or greater. Do not use it as an equality test; usestd::equal(C++98, with a four-iterator overload since C++14) orstd::ranges::equal(C++20). - Non-strict comparators. Passing
<=orstd::less_equalviolates the strict weak ordering requirement. Equal ranges then returntrue, and the behaviour is undefined. - Floating-point NaN. With NaN present,
<is not a strict weak ordering. Results are unspecified in meaning, and comparison may not be transitive. - Locale-unaware or signed-char comparison. Hand-written
charcomparators withstd::tolowerneed theunsigned charcast. This ordering is also not a linguistic collation. For user-visible sorting, usestd::collateor ICU. - Raw C strings.
std::lexicographical_compare(s1, s1 + n1, s2, s2 + n2)works, but you must supply the lengths. Passings1, s1 + strlen(s1)repeatedly is quadratic in loops. - Mixing signed and unsigned element types. Comparing
intandunsignedranges applies the usual arithmetic conversions, so-1compares greater than1u. Usestd::cmp_less(C++20) in a custom comparator. - Lexicographic is not numeric.
"10" < "9"istrueas strings. For numbers, version strings, and natural ordering, parse first. - Overload ambiguity with policies. If the first argument is an execution policy, the parallel overload requires forward iterators. Input iterators fail to compile.
See Also
- min-max:
std::min/std::maxuse the same strict<convention. - merge: merging relies on a consistent strict weak ordering.
- binary-search: sorted-range queries with the same comparator requirements.