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:

  1. Proxy Iterators: Types like std::vector<bool>::iterator or modern range view iterators (such as those produced by std::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.
  2. 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:

  1. ADL-found iter_swap: The CPO first looks for an unqualified call to iter_swap(a, b) using Argument-Dependent Lookup (ADL). This allows authors of custom iterator types to define specialized swapping logic.

  2. iter_move via Dereference: If no custom iter_swap exists, it attempts to use std::ranges::iter_move to 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_move are correctly swapped.

  3. Fallback to std::swap: If neither customization applies, it falls back to exchanging the dereferenced values via std::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 that ranges::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.