Guides

How to Reverse a Vector in C++: Copies, Iterators, and Views

Reverse std::vector in place, traverse backward, create copies, use reverse_copy safely, and distinguish C++20 algorithms from lazy views.

By Text Reverser TeamNovember 4, 20267 min read

Reversing a std::vector in C++ is usually a one-line operation, but the right operation depends on what “reverse” means in your program. Do you want to change the vector, inspect its elements from back to front, create a second vector, or expose a lazy C++20 range? Those choices differ in ownership, iterator lifetime, and whether the original order survives.

This guide starts with the standard algorithm for in-place reversal, then covers reverse iterators, copying, move-only elements, and C++20 views. The short examples show required headers and statements to place inside a function; the complete C++17 program near the end can be compiled as provided. For character-level behavior, see reversing a string in C++. Reordering vector elements and choosing safe character boundaries are different tasks.

The default: reverse the vector in place

When the vector’s stored order should change, include <algorithm> and pass its half-open iterator range:

#include <algorithm>
#include <vector>

std::vector<int> values{3, 1, 2};
std::reverse(values.begin(), values.end());
// values is now {2, 1, 3}

std::reverse returns void. It swaps corresponding pairs: first with last, second with second-to-last, continuing toward the middle. The range [first, last) includes first but excludes the past-the-end iterator last. Passing end() is correct; the algorithm does not dereference that sentinel.

This is not a vector member function. The algorithm works with bidirectional iterators and suitable swappable elements; vector iterators satisfy the iterator requirement. The std::reverse reference specifies exactly half the range length, rounded down, in swaps. For ordinary vector elements, reversal takes linear time and constant auxiliary space. That does not make a custom element’s swap or move operation free.

Reversing {3, 1, 2} produces {2, 1, 3}, not descending {3, 2, 1}. Reversal mirrors existing positions without comparing values. Sorting is a separate operation when numeric ranking or alphabetical order is required. Negative and duplicate values need no special treatment.

Half-open ranges prevent off-by-one errors

Consider reversing part of a five-element vector:

std::vector<int> values{0, 1, 2, 3, 4};
std::reverse(values.begin() + 1, values.begin() + 4);
// values is {0, 3, 2, 1, 4}

The first iterator points at 1. The second points at 4, which is excluded. Only the three values 1, 2, 3 move. This example assumes the shown five elements exist; offsets derived from user input must be validated before constructing out-of-range iterators.

Do not replace end() with end() - 1 for a full reversal. That would exclude the last element, and subtracting one from the end of an empty vector is invalid. Equal endpoints naturally represent an empty range. std::reverse(values.begin(), values.end()) therefore handles both empty and single-element vectors without a special branch.

For a vector of vectors, reversing the outer range changes row order only. It does not reverse each row’s contents or flatten the data. To change both levels, reverse the outer vector and separately reverse each inner vector. Make that distinction explicit before applying an otherwise correct algorithm.

Read a vector backward without changing its order

Reverse iterators change traversal direction, not stored positions. For a nonempty vector, rbegin() starts at the last element; rend() is the stopping sentinel. Read-only traversal uses the const forms:

for (auto it = values.crbegin(); it != values.crend(); ++it) {
    // Read *it here; the vector's order is unchanged.
}

Use crbegin() and crend() when the loop should not modify elements through the iterator. Use rbegin() and rend() when writable access is intentional and the source is non-const. Dereference only a valid element iterator, never rend(). These APIs are available in C++11 and later.

This avoids a common unsigned-index bug. In a loop starting with std::size_t i = values.size() - 1, the subtraction underflows for an empty vector. A condition such as i >= 0 does not stop an unsigned counter after zero either. Reverse iterators express the bounds directly, without that arithmetic trap.

Traversal is not a snapshot. Avoid operations that invalidate iterators during the loop. push_back and emplace_back invalidate all vector iterators if reallocation occurs; even without reallocation, the old end() is invalidated. A reverse iterator based on that endpoint is affected. Consult the vector iterator-invalidation reference when changing container structure.

In-place swapping with std::reverse does not reallocate vector storage. An existing iterator still identifies a position, but the logical value at that position may have changed. Iterator validity and the identity of the value you intended to track are separate questions.

Merely choosing reverse iterators does not guarantee non-mutation. Calling std::reverse(values.rbegin(), values.rend()) still swaps the vector’s elements and reverses its stored order. The iterator pair describes the range presented to the algorithm; the algorithm decides what happens to that range. Prefer ordinary begin() and end() for an intentional in-place reversal so readers do not mistake the call for read-only traversal.

A const vector cannot be passed to a mutating reversal of ordinary integer elements because those elements cannot be swapped through const access. Do not cast away constness to silence that error. Either traverse the source backward or construct a separate reversed vector. A function accepting a const reference makes a read-only contract clearer to its callers.

Construct a reversed copy

When source order must survive, use the reverse-iterator pair as a range constructor:

std::vector<int> original{3, 1, 2};
std::vector<int> reversed(original.rbegin(), original.rend());
// original: {3, 1, 2}; reversed: {2, 1, 3}

Alternatively, copy first and reverse that separate vector:

std::vector<int> reversed = original;
std::reverse(reversed.begin(), reversed.end());

These are independent alternatives, not statements to paste together with the same variable name. Choose the range constructor when the intent is specifically to construct a backward sequence. Copying first can be clearer when the copy will undergo several transformations. Both allocate storage for another vector and perform linear element work.

Copy semantics follow the element type. A std::vector<Record> copies records according to their copy constructors; the records may themselves contain handles or shared state. A std::vector<std::shared_ptr<Record>> copies shared pointers, so both vectors share ownership of the same records. There is no universal rule that every vector copy deeply duplicates everything reachable from its elements.

Conversely, std::unique_ptr<Record> is move-only. A normal copying range constructor or reverse_copy cannot copy those pointers. In-place std::reverse can swap them, but creating a separate reversed vector of unique pointers requires an explicit decision about transferring ownership. Do not hide that transfer behind a helper described as preserving the source.

Use reverse_copy with a distinct destination

An output iterator is useful when an existing pipeline needs a separately stored reversed sequence:

#include <algorithm>
#include <iterator>
#include <vector>

std::vector<int> source{3, 1, 2};
std::vector<int> output;
output.reserve(source.size());
std::reverse_copy(source.begin(), source.end(), std::back_inserter(output));
// output is {2, 1, 3}

reserve provides capacity, not constructed elements. std::back_inserter calls push_back, so it correctly grows the initially empty destination. Writing through output.begin() instead requires actual elements, for example by constructing std::vector<int> output(source.size()). Reserved capacity alone does not create valid element positions.

Source and destination ranges must not overlap. The reverse_copy documentation specifies linear copying work and undefined behavior for overlapping ranges. It is not an in-place reversal tool. Use std::reverse for mutation, or provide a distinct destination whose elements can receive the copied values.

C++20: a mutating algorithm versus a lazy view

C++20 offers a range-taking algorithm:

#include <algorithm>
#include <vector>

std::vector<int> values{3, 1, 2};
auto finish = std::ranges::reverse(values);
// values is {2, 1, 3}; finish equals values.end().

For this lvalue vector, the range overload returns its past-the-end iterator and still mutates the elements. Compile as C++20 and include <algorithm>. std::reverse(values) is not the iterator-pair spelling shown earlier. The ranges::reverse reference describes its bidirectional and permutable requirements.

A view is different. Save this complete example as reverse_vector_view.cpp:

#include <iostream>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> values{3, 1, 2};
    for (int value : values | std::views::reverse) {
        std::cout << value << ' ';
    }
    std::cout << std::endl;
}

Compile with g++ -std=c++20 -Wall -Wextra -pedantic reverse_vector_view.cpp -o reverse_vector_view and run ./reverse_vector_view. It prints 2 1 3 without reordering the vector. This requires a compiler and standard library supporting these C++20 ranges facilities.

For the lvalue vector shown here, the view refers to the existing vector rather than owning or copying it. The vector must remain alive whenever the view is used. A view over a non-const vector can expose writable references; assigning through its iterator changes the underlying element. Merely making the view object const does not necessarily make its elements read-only. Use a const source when read-only access is required.

The reverse_view reference explains its underlying range relationship. Do not return a view referring to a destroyed local vector, and do not structurally alter the vector during traversal. This is a window onto current data, not a frozen reversed result. A later traversal can observe changes to the vector.

Compile a complete C++17 example with checks

Save the following as reverse_vector.cpp. Its checks throw an exception on failure rather than depending on whether assertions are enabled:

#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <vector>

void check(const std::vector<int>& actual, const std::vector<int>& expected) {
    if (actual != expected) {
        throw std::runtime_error("vector check failed");
    }
}

void print(const std::vector<int>& values) {
    std::cout << '{';
    for (std::size_t i = 0; i < values.size(); ++i) {
        if (i != 0) std::cout << ", ";
        std::cout << values[i];
    }
    std::cout << '}' << std::endl;
}

int main() {
    std::vector<int> values{3, 1, 2};
    std::reverse(values.begin(), values.end());
    check(values, {2, 1, 3});
    std::cout << "in place: ";
    print(values);

    std::vector<int> subrange{0, 1, 2, 3, 4};
    std::reverse(subrange.begin() + 1, subrange.begin() + 4);
    check(subrange, {0, 3, 2, 1, 4});
    std::cout << "subrange: ";
    print(subrange);

    const std::vector<int> original{3, 1, 2};
    std::vector<int> reversed(original.rbegin(), original.rend());
    check(original, {3, 1, 2});
    check(reversed, {2, 1, 3});
    std::cout << "copy: ";
    print(reversed);

    std::cout << "backward:";
    for (auto it = original.crbegin(); it != original.crend(); ++it) {
        std::cout << ' ' << *it;
    }
    std::cout << std::endl;

    std::vector<int> empty;
    std::reverse(empty.begin(), empty.end());
    check(empty, {});
    std::cout << "empty: {}" << std::endl;
}

Compile and run:

g++ -std=c++17 -Wall -Wextra -pedantic reverse_vector.cpp -o reverse_vector
./reverse_vector

Expected output:

in place: {2, 1, 3}
subrange: {0, 3, 2, 1, 4}
copy: {2, 1, 3}
backward: 2 1 3
empty: {}

The examples were tested in C++17 and C++20 modes as appropriate. Extend checks for your own element types, including singleton vectors, duplicates, odd and even lengths, negative values, and repeated reversal. Two in-place reversals should restore the original order. Test copy behavior separately from pointer ownership and iterator lifetime.

Choose by mutation, copying, or traversal

Use std::reverse when the vector itself should change. Use reverse iterators when you only need to visit it backward. Construct from reverse iterators or copy then reverse for a separately stored sequence; use reverse_copy when a distinct output destination fits the surrounding algorithm. Choose C++20 views only when lazy traversal and the source’s lifetime are understood.

For a vector of words, these operations preserve each string’s spelling while changing sequence order. See reversing words without scrambling their letters for that distinction, or use the Text Reverser tool for a one-off text transformation.

When debugging, check the kind of operation before rewriting the swap logic. An unchanged vector may mean you only created a traversal view. A copying compilation error may mean your element type is move-only. A later iterator failure may mean the source was destroyed or reallocated. Separating these questions makes C++ vector reversal predictable instead of treating every backward-looking operation as interchangeable.

Try the Text Reverser tool

Reverse text, words, or letters instantly — free and entirely in your browser.

Open the tool

Permalink: https://www.textreverser.me/blog/cpp-reverse-vector