Skip to content
C++
Library
since C++98
Intermediate

<utility>

The <utility> header provides foundational C++ building blocks — move, forward, swap, exchange, pair, declval, and safe integer comparisons.

<utility>since C++98

A standard library header providing general-purpose primitives for move semantics, perfect forwarding, object exchange, pairs, and — since C++20 — safe integer comparison functions.

Overview

<utility> is the catch-all header for small but foundational building blocks that don't belong to any single data-structure or algorithm category. Its surface area has grown with every standard:

AdditionStandard
std::pair, std::swap (copy-based)C++98
std::move, std::forward, std::move_if_noexcept, std::declval, std::swap (move-based)C++11
std::exchange, std::integer_sequenceC++14
std::as_const, std::in_place* tagsC++17
std::cmp_*, std::in_rangeC++20
std::to_underlying, std::forward_like, std::unreachableC++23

Syntax

cpp
// Move semantics primitives (C++11)
template<typename T> constexpr std::remove_reference_t<T>&& move(T&&) noexcept;
template<typename T> constexpr T&&                          forward(std::remove_reference_t<T>&) noexcept;
template<typename T> constexpr T&&                          move_if_noexcept(T&) noexcept;

// Swap and exchange (C++98 / C++14)
template<typename T>           void swap(T&, T&) noexcept(/*see below*/); // C++98, move-based since C++11
template<typename T, typename U = T> T exchange(T&, U&&);                 // C++14

// Pair (C++98)
template<typename T1, typename T2> struct pair;
template<typename T1, typename T2> constexpr pair<T1,T2> make_pair(T1&&, T2&&);

// Type utilities
template<typename T> constexpr std::add_const_t<T>& as_const(T&) noexcept;  // C++17
template<typename T> typename std::add_rvalue_reference<T>::type declval() noexcept; // C++11, unevaluated only

// Safe integer comparison (C++20)
template<typename T, typename U> constexpr bool cmp_equal(T, U) noexcept;
template<typename T, typename U> constexpr bool cmp_less(T, U) noexcept;
template<typename T>             constexpr bool in_range(std::intmax_t) noexcept;

// Enum utility (C++23)
template<typename Enum> constexpr std::underlying_type_t<Enum> to_underlying(Enum) noexcept;

Examples

Move semantics and perfect forwarding

std::move does not move anything — it produces an rvalue reference, enabling move constructors and move assignment operators to be selected by overload resolution. std::forward preserves the value category of a forwarding reference, enabling perfect forwarding in templates.

cpp
#include <utility>
#include <vector>
#include <string>

class Buffer {
    std::vector<std::byte> data_;
public:
    Buffer(Buffer&& other) noexcept
        : data_(std::move(other.data_))  // C++11: cast to rvalue, triggers vector move ctor
    {}

    Buffer& operator=(Buffer&& other) noexcept {
        data_ = std::move(other.data_);
        return *this;
    }
};

// Perfect forwarding factory (C++11)
template<typename T, typename... Args>
T create(Args&&... args) {
    return T(std::forward<Args>(args)...);
}

auto s = create<std::string>(5, 'x');  // forwards (size_t, char) — no copies

std::exchange in move constructors

std::exchange (C++14) replaces a value and returns the old one. It eliminates a common pattern in move constructors where you null out the source and need the original value.

cpp
#include <utility>

class Handle {
    int fd_ = -1;
public:
    Handle(int fd) : fd_(fd) {}

    Handle(Handle&& other) noexcept
        : fd_(std::exchange(other.fd_, -1))  // C++14: take fd_, set other.fd_ to -1 atomically
    {}

    Handle& operator=(Handle&& other) noexcept {
        if (this != &other) {
            close_if_open();
            fd_ = std::exchange(other.fd_, -1);
        }
        return *this;
    }

    ~Handle() { close_if_open(); }
private:
    void close_if_open() { if (fd_ != -1) ::close(fd_); }
};

The swap customisation point

std::swap is a customisation point. The correct way to write a swap function for your type is as a non-member in the same namespace, not as a specialisation of std::swap. Call it with the using-declaration pattern to allow ADL to find your overload first, then fall back to the generic template:

cpp
#include <utility>

namespace geo {

struct Point { double x, y; };

void swap(Point& a, Point& b) noexcept {
    using std::swap;         // ADL fallback for members that don't have their own swap
    swap(a.x, b.x);
    swap(a.y, b.y);
}

}  // namespace geo

// At the call site, always invoke swap with the using pattern:
void sort_two(geo::Point& a, geo::Point& b) {
    using std::swap;
    if (b.x < a.x) swap(a, b);  // finds geo::swap via ADL
}

std::pair

std::pair<T1, T2> stores two heterogeneous values in first and second. It is comparable, copyable, movable, and swappable. Since C++17 it participates in structured bindings.

cpp
#include <utility>
#include <map>
#include <string>

std::map<std::string, int> word_count;
auto [it, inserted] = word_count.emplace("hello", 1);  // C++17 structured binding on pair

// make_pair deduces types; direct construction is often cleaner in C++11+
std::pair<std::string, int> entry{"world", 42};

// std::tie extracts into existing variables (C++11); prefer structured bindings in C++17+
std::string key;
int value;
std::tie(key, value) = entry;  // C++11

std::declval — unevaluated context

std::declval<T>() produces an rvalue reference to T without requiring a constructor. It is only valid in unevaluated operands (decltype, sizeof, noexcept, requires). Use it to query expression types for types that aren't default-constructible.

cpp
#include <utility>
#include <type_traits>

template<typename Container>
using element_type = decltype(*std::declval<Container>().begin());

static_assert(std::is_same_v<element_type<std::vector<int>>, int&>);

// SFINAE: detect whether T + U is well-formed (C++11/14 style)
template<typename T, typename U, typename = void>
struct can_add : std::false_type {};

template<typename T, typename U>
struct can_add<T, U, std::void_t<decltype(std::declval<T>() + std::declval<U>())>>
    : std::true_type {};

Safe integer comparison (C++20)

Comparing signed and unsigned integers directly triggers undefined behaviour or produces surprising results due to implicit conversions. The std::cmp_* family performs mathematically correct comparisons regardless of signedness.

cpp
#include <utility>
#include <cstddef>

void check_index(int idx, std::vector<int>& v) {
    // Dangerous: if idx < 0, the comparison is UB or silently wrong
    // if (idx < v.size()) { ... }

    // Correct (C++20):
    if (std::cmp_greater_equal(idx, 0) && std::cmp_less(idx, v.size())) {
        v[idx] = 0;
    }

    // std::in_range checks whether a value fits in the target type (C++20)
    static_assert(std::in_range<unsigned char>(255));
    static_assert(!std::in_range<unsigned char>(256));
}

std::to_underlying (C++23)

Converts a scoped or unscoped enum to its underlying integer type without static_cast boilerplate:

cpp
#include <utility>
#include <cstdint>

enum class Color : uint8_t { Red = 1, Green = 2, Blue = 4 };

void set_flags(uint8_t& reg, Color c) {
    reg |= std::to_underlying(c);  // C++23 — cleaner than static_cast<uint8_t>(c)
}

Prior to C++23 the equivalent was static_cast<std::underlying_type_t<Color>>(c).

Best Practices

Prefer std::exchange over manual null-and-take in move operations. It makes the intent clear, is atomic in the sense that it can't leave the member in an intermediate state across two statements, and works as a one-liner.

Always use the using std::swap; + unqualified swap() pattern at every call site that swaps user-defined types. Calling std::swap directly defeats ADL and bypasses your type's optimised overload.

Use std::cmp_* whenever mixing signed and unsigned. The compiler warning -Wsign-compare fires on direct comparison, but the code may still compile. cmp_less and friends make the safety guarantee explicit in the type system.

Annotate move operations noexcept where possible. std::vector and other containers check std::is_nothrow_move_constructible to decide whether to move or copy during reallocation. Unannotated move constructors force copies.

Common Pitfalls

std::move on a named rvalue reference still needs std::move. Inside a function that takes T&&, the parameter is an lvalue. Forwarding it without std::move copies:

cpp
void bad(std::string&& s) {
    consume(s);             // copies — s is an lvalue here
}
void good(std::string&& s) {
    consume(std::move(s));  // moves
}

std::forward outside a deduced forwarding reference context is wrong. Calling std::forward<T> where T is not a deduced template parameter produces incorrect value categories. It is only meaningful inside a template where T&& was deduced.

std::pair::first and second are public data members, not functions. Calling .first() compiles only if a callable is stored — otherwise it is a silent type error or a hard compile failure depending on context.

std::declval in an evaluated context is ODR-ill-formed. It has no definition; using it outside decltype or a requires-expression will cause a linker error, not a compile error.

std::exchange

std::exchange(obj, new_val) atomically replaces obj with new_val and returns the old value. This makes it the canonical tool for implementing move constructors and move assignment operators — the source is zeroed out in a single expression rather than two separate statements.

cpp
#include <utility>

class Resource {
    void* ptr_ = nullptr;
public:
    // C++14: move constructor using std::exchange
    Resource(Resource&& other) noexcept
        : ptr_(std::exchange(other.ptr_, nullptr))  // C++14
    {}

    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            release();
            ptr_ = std::exchange(other.ptr_, nullptr);  // C++14
        }
        return *this;
    }

    ~Resource() { release(); }
private:
    void release() { if (ptr_) { free(ptr_); ptr_ = nullptr; } }
};

The manual equivalent requires two statements and risks leaving the object in an intermediate state if an exception fires between them. std::exchange is also useful in state-machine resets where you need the previous value before installing a new one.

cpp
#include <utility>

struct Parser {
    int result_ = 0;

    // Reset result_ to Default{} and return the accumulated value  // C++14
    int flush() {
        return std::exchange(result_, 0);  // C++14
    }
};

// Compare to manual swap — std::exchange is more expressive when old value is needed
// old = x; x = new_val;  →  old = std::exchange(x, new_val);  // C++14

std::as_const and std::move_if_noexcept

std::as_const(x) returns a const T& to x, forcing the compiler to select the const overload without a cast. This is especially valuable in generic code where you want to verify the const path compiles, or where implicit conversion might otherwise pick the non-const overload.

cpp
#include <utility>
#include <type_traits>

struct Logger {
    void log()       { /* non-const: logs with side-effect counter */ }
    void log() const { /* const: read-only snapshot log             */ }
};

void use(Logger& lg) {
    lg.log();                // non-const overload
    std::as_const(lg).log(); // const overload — no cast needed  // C++17
}

std::move_if_noexcept(x) produces an rvalue reference only when the move constructor is noexcept; otherwise it produces an lvalue reference, triggering a copy. This is exactly the policy std::vector uses during reallocation to preserve the strong exception guarantee: if moving can throw, it copies to ensure rollback is possible.

cpp
#include <utility>
#include <type_traits>

struct SafeValue {
    SafeValue(SafeValue&&) noexcept = default;  // noexcept move — will be moved
};

struct UnsafeValue {
    UnsafeValue(UnsafeValue&&) {}               // may throw — will be copied
};

static_assert(std::is_nothrow_move_constructible_v<SafeValue>);   // C++17
static_assert(!std::is_nothrow_move_constructible_v<UnsafeValue>); // C++17

void reallocate(SafeValue& s, UnsafeValue& u) {
    auto&& a = std::move_if_noexcept(s);  // C++17: rvalue ref — move will be selected
    auto&& b = std::move_if_noexcept(u);  // C++17: lvalue ref — copy will be selected
}

std::to_underlying (C++23)

std::to_underlying(e) is the clean, intent-revealing way to extract the underlying integer from a scoped enum. Prior to C++23 this required static_cast<std::underlying_type_t<Color>>(e) — verbose enough that developers sometimes used reinterpret_cast or C-style casts by mistake.

cpp
#include <utility>
#include <cstdint>

enum class Color : uint8_t { Red = 1, Green = 2, Blue = 4 };

// Pre-C++23: verbose and error-prone
uint8_t old_way = static_cast<std::underlying_type_t<Color>>(Color::Red);

// C++23: clear and concise
uint8_t new_way = std::to_underlying(Color::Red);  // C++23

Serialization is a common use case where enum values must be written as raw bytes:

cpp
#include <utility>
#include <ostream>
#include <cstdint>
#include <format>

enum class Status : uint16_t { Ok = 200, NotFound = 404, Error = 500 };

void serialize(std::ostream& os, Status s) {
    uint16_t raw = std::to_underlying(s);  // C++23: write integer, not enum token
    os.write(reinterpret_cast<const char*>(&raw), sizeof(raw));
}

// std::format cannot print scoped enums directly; std::to_underlying bridges the gap
std::string describe(Status s) {
    return std::format("HTTP status: {}", std::to_underlying(s));  // C++23
}

std::pair and std::tuple Operations

std::pair<T1, T2> is the simplest heterogeneous aggregate in the standard library, and its API has been progressively enhanced to align with modern C++ idioms. Prefer std::pair{a, b} (CTAD, C++17) over std::make_pair(a, b) in new code — CTAD is shorter and avoids an extra function call.

cpp
#include <utility>
#include <string>

// C++11: make_pair deduces types
auto p1 = std::make_pair(std::string{"key"}, 42);   // C++11

// C++17: CTAD — same result, less boilerplate
std::pair p2{std::string{"key"}, 42};                // C++17

Type-based std::get (C++14) avoids error-prone numeric indices when one of the types is unique:

cpp
#include <utility>
#include <tuple>
#include <string>

std::pair<std::string, int> entry{"hello", 7};

auto& name  = std::get<std::string>(entry);  // C++14: type-based access
auto  count = std::get<1>(entry);            // C++11: index-based access

// std::tie unpacks into existing variables (C++11)
std::string k; int v;
std::tie(k, v) = entry;                      // C++11

// Structured bindings are the preferred modern alternative (C++17)
auto [key, val] = entry;                     // C++17

Pairs compare lexicographically, and std::tuple_cat merges two or more tuples (including pairs) into a single tuple:

cpp
#include <utility>
#include <tuple>

std::pair<int,int> a{1, 2}, b{1, 3};
static_assert(a < b);  // C++11: lexicographic comparison

// Merge pairs or tuples into a single tuple (C++11)
auto merged = std::tuple_cat(a, b);           // C++11: tuple<int,int,int,int>{1,2,1,3}
auto [x1, y1, x2, y2] = merged;              // C++17: unpack merged tuple

std::integer_sequence and Compile-Time Index Generation

std::integer_sequence<T, Ns...> and its convenience alias std::index_sequence<Ns...> carry a compile-time list of integers as a type, enabling pack expansion over indices. The matching factory std::make_index_sequence<N> generates index_sequence<0, 1, ..., N-1> without manual template recursion.

cpp
#include <utility>
#include <tuple>
#include <type_traits>

// C++14: index_sequence as a tag type used in a helper overload
template<typename F, typename Tuple, std::size_t... Is>
auto apply_impl(F&& f, Tuple&& t, std::index_sequence<Is...>)  // C++14
    -> decltype(std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t))...))
{
    return std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t))...);
}

template<typename F, typename Tuple>
auto apply(F&& f, Tuple&& t)  // C++14
{
    constexpr auto N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
    return apply_impl(std::forward<F>(f),
                      std::forward<Tuple>(t),
                      std::make_index_sequence<N>{});  // C++14
}
// Note: std::apply is in <tuple> since C++17; this example shows the technique.

The index-sequence idiom enables compile-time dispatch that collapses to a single function call at runtime with zero overhead:

cpp
#include <utility>
#include <array>

// C++14: expand array elements as individual arguments
template<typename F, std::size_t N, std::size_t... Is>
void foreach_impl(F&& f, const std::array<int, N>& arr,
                  std::index_sequence<Is...>)  // C++14
{
    (f(arr[Is]), ...);  // C++17 fold — unrolled at compile time
}

template<typename F, std::size_t N>
void foreach_array(F&& f, const std::array<int, N>& arr) {
    foreach_impl(std::forward<F>(f), arr,
                 std::make_index_sequence<N>{});  // C++14
}

std::unreachable and std::assume (C++23)

std::unreachable() marks a code path the programmer guarantees will never be executed; reaching it is undefined behaviour, which the compiler exploits for optimisation and dead-code elimination. It is the portable replacement for GCC/Clang's __builtin_unreachable() and MSVC's __assume(false).

cpp
#include <utility>   // std::unreachable (C++23)
#include <cassert>

enum class Direction { North, South, East, West };

int delta_x(Direction d) {
    switch (d) {
        case Direction::North: return  0;
        case Direction::South: return  0;
        case Direction::East:  return  1;
        case Direction::West:  return -1;
        default: std::unreachable();  // C++23: silences -Wreturn-type, enables optimisation
    }
}

[[assume(cond)]] (C++23) tells the compiler that cond is always true at that point — without emitting a runtime check. This differs from assert() (which is a runtime check, stripped in release builds) and from std::unreachable() (which marks a path, not a condition).

cpp
#include <utility>

// C++23: [[assume]] vs assert() vs std::unreachable()
void process(int* ptr, std::size_t n) {
    [[assume(ptr != nullptr)]];  // C++23: no branch generated; optimizer may hoist loads
    [[assume(n > 0)]];           // C++23: loop can skip empty-range guard

    for (std::size_t i = 0; i < n; ++i)
        ptr[i] *= 2;
}

// assert() fires at runtime in debug builds, is removed in release — not a hint to the optimizer
// __builtin_unreachable() is GCC/Clang only; std::unreachable() is the portable C++23 equivalent

See Also

  • reference/idioms/value-semantics — ownership and move semantics design patterns
  • reference/language/decltype — decltype specifier used alongside std::declval
  • <tuple> — generalisation of std::pair to N elements
  • <type_traits> — type predicates used with <utility> utilities