std::iter_swap in C++ Range-Aware Algorithms
In modern C++, range-aware algorithms require a reliable way to
exchange elements referenced by iterators without assuming those
iterators behave like standard raw pointers. The utility
std::iter_swap—and specifically its modern range-adapted
counterpart std::ranges::iter_swap—acts as a crucial
customization point for swapping the values pointed to by two iterators.
It ensures that range algorithms execute correctly, safely, and
efficiently, especially when dealing with non-standard reference types,
proxy iterators, and custom user-defined data structures.
The Limitation of Direct Dereference Swapping
A naive implementation of swapping two iterator elements relies on
dereferencing: std::swap(*a, *b). While this works for
standard contiguous or node-based iterators returning true lvalue
references (T&), it fails in more complex generic
programming scenarios:
- Proxy Iterators: Types like
std::vector<bool>::iteratoror modern range view iterators (such as those produced bystd::views::zip) return proxy objects by value rather than genuine lvalue references. Direct swapping of these temporary proxies often does not alter the underlying container's state. - Move-Only and Custom Types: Some data structures provide specialized, highly optimized logic for exchanging elements without invoking expensive copy or move operations on the contained objects.
The Mechanism of
std::ranges::iter_swap
In C++20, the Ranges library formalized iterator swapping by
introducing std::ranges::iter_swap as a Customization Point
Object (CPO). When a range-aware algorithm needs to swap elements, it
invokes this CPO, which evaluates candidates in a strict priority
order:
ADL-found
iter_swap: The CPO first looks for an unqualified call toiter_swap(a, b)using Argument-Dependent Lookup (ADL). This allows authors of custom iterator types to define specialized swapping logic.iter_movevia Dereference: If no customiter_swapexists, it attempts to usestd::ranges::iter_moveto extract the values:auto temp = ranges::iter_move(a); *a = ranges::iter_move(b); *b = std::move(temp);This ensures that proxy references capable of reading and writing via
iter_moveare correctly swapped.Fallback to
std::swap: If neither customization applies, it falls back to exchanging the dereferenced values viastd::ranges::swap(*a, *b).
If none of these expressions are valid, the call is rejected at compile time, preventing undefined behavior.
Role in Standard Range Algorithms
Range-aware algorithms—such as std::ranges::sort,
std::ranges::reverse, std::ranges::partition,
and std::ranges::rotate—depend internally on
std::ranges::iter_swap.
By decoupling element swapping from raw dereferencing, the Ranges library guarantees that:
- Views Remain Composable: View pipelines that
dynamically generate elements or combine multiple sequences can be
partitioned or sorted in-place if their underlying iterators model
std::indirectly_swappable. - Concept Enforcement: The compiler verifies
algorithm prerequisites at compile time using the
std::indirectly_swappable<I1, I2>concept, which requires thatranges::iter_swap(i1, i2)is valid and semantic invariants are maintained. - Performance Optimization: Algorithms automatically invoke specialized internal memory transfers when provided by custom iterators, bypassing default construction and assignment overheads.
Ultimately, std::iter_swap and
std::ranges::iter_swap bridge the gap between abstract
iterator traversals and concrete data manipulation, enabling range-aware
algorithms to operate predictably across both standard containers and
sophisticated user-defined ranges.