C++ Copying Algorithms

An introduction to the 7 movement algorithms in the C++ standard library: copy, copy_n, copy_if, copy_backward, reverse_copy, rotate_copy, and unique_copy
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

04c.jpg
Ryan McCombe
Ryan McCombe
Posted

In this lesson, we cover the most essential copying algorithms. We’ll cover the seven main algorithms for copying objects from one container to another: copy, copy_backward, rotate_copy, reverse_copy, copy_if, copy_n and unique_copy.

All the algorithms in this section are available within the <algorithm> header:

#include <algorithm>

What it means for an object to be copied is defined by the copy semantics of its type. We cover copy semantics in more detail here:

std::ranges::copy

The standard copy algorithm will copy every element from a source range to a new destination. The destination where elements will be copied to is defined by passing an iterator as the second argument.

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

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::ranges::copy(Source, Destination.begin());

  for (auto i : Destination) {
    std::cout << i;
  }
}
123000

We do not need to copy to the beginning of the container. We can provide an iterator that points anywhere:

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

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::ranges::copy(Source,
                    Destination.begin() + 2);

  for (auto i : Destination) {
    std::cout << i;
  }
}
001230

We are responsible for ensuring that the location the destination iterator points to has enough space to receive our copies.

The type returned from std::ranges::copy is a std::ranges::in_out_result which is commonly aliased to types like std::ranges::copy_result. This type has two fields:

  • in: A past-the-end iterator for our input range
  • out: A past-the-end iterator for the last element copied to our output range
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::ranges::copy_result Result{
      std::ranges::copy(Source,
                        Destination.begin())};

  int64_t InputSize{
      std::distance(Source.begin(), Result.in)};

  std::cout << "The input size was " << InputSize;

  int64_t OutputPosition{std::distance(
      Destination.begin(), Result.out - 1)};

  std::cout << "\nThe last element copied is at "
               "position "
            << OutputPosition
            << " in the destination";

  std::cout << "\nIts value is "
            << Destination[OutputPosition];
}
The input size was 3
The last element copied is at position 2 in the destination
Its value is 3

The std::ranges::in_out_result type supports structured binding for easier use:

auto [in, out]{std::ranges::copy(
      Source, Destination.begin())};

Iterator-Based Algorithms

In this lesson, we’re focusing on the versions of these algorithms that work with ranges, a C++20 feature.

Alternative versions of these algorithms work directly with iterators, rather than ranges.

These alternative functions are also in the <algorithm> library and can be used by excluding the ranges namespace from our identifier.

Below, we rewrite the first example to use std::copy instead of std::ranges::copy. Instead of passing the source as a range, we pass two iterators, representing the first and last element to copy:

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

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::copy(Source.begin(), Source.end(),
            Destination.begin());

  for (auto i : Destination) {
    std::cout << i;
  }
}
123000

std::ranges::copy_n

The std::ranges::copy_n algorithm copies a limited number of objects from a source range to a destination range. The function has 3 parameters:

  • An iterator pointing to the beginning of the source range
  • An integer, representing how many objects to copy
  • An iterator pointing to the start of the destination range, where objects are to be copied
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Source{1, 2, 3, 4, 5, 6};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::ranges::copy_n(Source.begin(), 4,
                      Destination.begin());

  for (auto i : Destination) {
    std::cout << i;
  }
}
123400

The std::ranges::copy_n algorithm returns a std::ranges::copy_n_result, which is an alias for std::ranges::in_out_result. This is a struct with two properties:

  • in - a past-the-end iterator for the last element copied within the source range
  • out - a past-the-end iterator for the last element copied within the destination range
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Source{1, 2, 3, 4, 5, 6};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  auto [in, out]{std::ranges::copy_n(
      Source.begin(), 4, Destination.begin())};

  std::cout << "Values in Destination: ";
  for (auto& Num : Destination) {
    std::cout << Num;
  }

  std::cout
      << "\nThe input iterator is at position "
      << std::distance(Source.begin(), in);

  std::cout << "\nThe output iterator is at "
               "position "
            << std::distance(
                   Destination.begin(), out);

  std::cout << "\nThe last element copied was "
            << *(out - 1);
}
Values in Destination: 123400
The input iterator is at position 4
The output iterator is at position 4
The last element copied was 4

std::ranges::copy_if

The std::ranges::copy_if algorithm works similarly to std::ranges::copy, but it accepts a predicate function as the third argument. Each object in the source range will be passed to the predicate, and it will be copied only if that predicate call returns true.

Below, we use std::ranges::copy_if to copy only the even numbers from the source range:

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

int main() {
  std::vector Source{1, 2, 3, 4, 5, 6};
  std::vector<int> Destination;
  Destination.resize(Source.size());

  auto isEven{
      [](const int& x) { return x % 2 == 0; }};

  std::ranges::copy_if(
      Source, Destination.begin(), isEven);

  std::cout << "     Source: ";
  for (auto i : Source) {
    std::cout << i;
  }

  std::cout << "\nDestination: ";
  for (auto i : Destination) {
    std::cout << i;
  }
}
Source:      123456
Destination: 246000

The type returned from std::ranges::copy_if is a std::ranges::in_out_result which is aliased as std::ranges::copy_if_result. This type has two fields:

  • in: A past-the-end iterator for our input range
  • out: A past-the-end iterator for the last element copied to our output range
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Source{1, 2, 3, 4, 5, 6};
  std::vector<int> Destination;
  Destination.resize(Source.size());

  auto isEven{
      [](const int& x) { return x % 2 == 0; }};

  auto [in, out]{std::ranges::copy_if(
      Source, Destination.begin(), isEven)};

  std::cout << "     Source: ";
  for (auto i : Source) {
    std::cout << i;
  }

  std::cout << "\nDestination: ";
  for (auto i : Destination) {
    std::cout << i;
  }

  std::cout << "\nThe input had "
            << std::distance(Source.begin(), in)
            << " objects\n";

  std::cout << "The output iterator is at "
               "position "
            << std::distance(
                   Destination.begin(), out);

  std::cout << "\nThe last element copied was "
            << *(out - 1);
}
Source:      123456
Destination: 246000
The input had 6 objects
The output iterator is at position 3
The last element copied was 6

The std::ranges::copy_if algorithm additionally accepts an optional 4th argument, for a projection function. We copy projection in more detail in a dedicated lesson:

Below, we project our source integer to a day of the week. If the corresponding day of the week starts with “T”, we copy the integer:

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

int main() {
  std::vector Days{"Monday",    "Tuesday",
                   "Wednesday", "Thursday",
                   "Friday",    "Saturday",
                   "Sunday"};
  std::vector Source{0, 1, 2, 3, 4, 5, 6};
  std::vector<int> Destination;
  Destination.resize(Source.size());

  auto startsWithT{[](const std::string& x) {
    return x.starts_with("T");
  }};

  auto toDay{[&Days](const int& x) {
    return Days[x];
  }};

  std::ranges::copy_if(Source,
                       Destination.begin(),
                       startsWithT, toDay);

  std::cout << "     Source: ";
  for (auto i : Source) {
    std::cout << i;
  }

  std::cout << "\nDestination: ";
  for (auto i : Destination) {
    std::cout << i;
  }
}
Source:      0123456
Destination: 1300000

std::ranges::copy_backward

The copy_backward algorithm accepts a source input range, and an iterator pointing at where we want the copies to end.

Elements from the source are copied in reverse order, with the destination iterator also moving in reverse order. The effect of this is that the relative order of the source elements is maintained within the destination:

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

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::ranges::copy_backward(Source,
                             Destination.end());

  for (auto i : Destination) {
    std::cout << i;
  }
}
000123

Similar to copy, the copy_backward algorithm returns a std::ranges::in_out_result, with the same properties:

  • in - A past-the-end iterator for the input range
  • out - An iterator for the destination range, pointing to the last element moved.

The fact elements are copied from right to left does have an implication for the out iterator though. The last element copied will be the leftmost element, so the out iterator will point at that element within the destination range:

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

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0};

  auto [in, out]{std::ranges::copy_backward(
      Source, Destination.end())};

  std::cout << "Values in Destination: ";
  for (auto& Num : Destination) {
    std::cout << Num;
  }

  std::cout << "\nThe input had "
            << std::distance(Source.begin(), in)
            << " objects\n";

  std::cout << "The output iterator is at "
               "position "
            << std::distance(
                   Destination.begin(), out);

  std::cout << "\nThe last element copied was "
            << *out;
}
Values in Destination: 00123
The input had 3 objects
The output iterator is at position 2
The last element copied was 1

std::ranges::reverse_copy

The reverse_copy algorithm accepts an input range and a destination iterator. The input range is copied from right to left, whilst the destination iterator moves forward. The effect of this is that the source elements are copied into the destination in reverse order:

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

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::ranges::reverse_copy(
      Source, Destination.begin());

  for (auto i : Destination) {
    std::cout << i;
  }
}
321000

The reverse_copy algorithm returns a struct with the type std::ranges::in_out_result. This type is aliased to std::ranges::reverse_copy_result. The output has two properties:

  • in - A past-the-end iterator for the input range
  • out - A past-the-end iterator for the copied input range, within the destination range
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Source{1, 2, 3};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  auto [in, out] = std::ranges::reverse_copy(
      Source, Destination.begin());

  for (auto i : Destination) {
    std::cout << i;
  }

  std::cout << "\nThe input had "
            << std::distance(Source.begin(), in)
            << " objects\n";

  std::cout << "The output iterator is at "
               "position "
            << std::distance(
                   Destination.begin(), out);

  std::cout << "\nThe last element copied was "
            << *(out - 1);
}
321000
The input had 3 objects
The output iterator is at position 3
The last element copied was 1

std::ranges::rotate_copy

We can think of rotation as shifting everything left or right within a container. Objects that fall off the edge are rotated around to the opposite edge. For example, imagine we had this container:

{1, 2, 3, 4, 5, 6}

Rotating the container by 1 step to the right would result in this:

{6, 1, 2, 3, 4, 5}

Everything moved one step to the right. The 6 had no room to move right, so it rotated around to the start.

The rotate_copy algorithm combines a rotation and a copy. Elements from a source container are copied to a destination container, in a rotated order. The algorithm has three arguments:

  • The source range where the objects are to be copied from
  • An iterator pointing at the first object we want to copy - ie, this argument controls the amount of rotation
  • An iterator pointing at where we should start copying objects to

Below, we tell rotate_copy we want the first object copied to be Source.end() - 1, which is the 6. Therefore, relative to the Source, the Destination receives the copies rotated by one position to the right:

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

int main() {
  std::vector Source{1, 2, 3, 4, 5, 6};
  std::vector Destination{0, 0, 0, 0, 0, 0};

  std::ranges::rotate_copy(Source,
                           Source.end() - 1,
                           Destination.begin());

  std::cout << "     Source: ";
  for (auto i : Source) {
    std::cout << i;
  }

  std::cout << "\nDestination: ";
  for (auto i : Destination) {
    std::cout << i;
  }
}
     Source: 123456
Destination: 612345

The rotate_copy algorithm returns a struct with the type std::ranges::in_out_result. This type is aliased to std::ranges::rotate_copy_result. The output has two properties:

  • in - A past-the-end iterator for the input range
  • out - A past-the-end iterator for the copied input range, within the destination range
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Source{1, 2, 3, 4, 5, 6};
  std::vector Destination{0, 0, 0, 0,
                          0, 0, 0, 0};

  auto [in, out]{std::ranges::rotate_copy(
      Source, Source.end() - 1,
      Destination.begin())};

  for (auto i : Destination) {
    std::cout << i;
  }

  std::cout << "\nThe input had "
            << std::distance(Source.begin(), in)
            << " objects\n";

  std::cout << "The output iterator is at "
               "position "
            << std::distance(
                   Destination.begin(), out);

  std::cout << "\nThe last element copied was "
            << *(out - 1);
}
61234500
The input had 6 objects
The output iterator is at position 6
The last element copied was 5

std::ranges::unique_copy

The unique_copy algorithm has a fairly niche use. It works similarly to std::ranges::copy, but will not copy any object that passed an equality check with the previous object.

For example, copying 1, 2, 2, 1 will yield 1, 2, 1. The second 2 is skipped as it was equal to the previous object in the original range. Only the first object in each group of equal objects will be copied. As such, copying 1, 2, 2, 2, 1 will also yield 1, 2, 1.

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

int main() {
  std::vector Source{1, 1, 2, 1, 2, 2, 2, 2};
  std::vector<int> Destination;
  Destination.resize(Source.size());

  std::ranges::unique_copy(Source,
                           Destination.begin());

  std::cout << "     Source: ";
  for (auto i : Source) {
    std::cout << i;
  }

  std::cout << "\nDestination: ";
  for (auto i : Destination) {
    std::cout << i;
  }
}
Source:      11212222
Destination: 12120000

The unique_copy algorithm returns a struct with the type std::ranges::in_out_result. This type is aliased to std::ranges::unique_copy_result. The output has two properties:

  • in - A past-the-end iterator for the input range
  • out - A past-the-end iterator for the copied input range, within the destination range
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  std::vector Source{1, 1, 2, 1, 2, 2, 2, 2};
  std::vector<int> Destination;
  Destination.resize(Source.size());

  auto [in, out]{std::ranges::unique_copy(
      Source, Destination.begin())};

  std::cout << "     Source: ";
  for (auto i : Source) {
    std::cout << i;
  }

  std::cout << "\nDestination: ";
  for (auto i : Destination) {
    std::cout << i;
  }

  std::cout << "\nThe input had "
            << std::distance(Source.begin(), in)
            << " objects\n";

  std::cout << "The output iterator is at "
               "position "
            << std::distance(
                   Destination.begin(), out);

  std::cout << "\nThe last element copied was "
            << *(out - 1);
}
     Source: 11212222
Destination: 12120000
The input had 8 objects
The output iterator is at position 4
The last element copied was 2

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Standard Library Algorithms
7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

C++ Replace, Remove, and Erase Algorithms

An overview of the key C++ standard library algorithms for replacing and removing objects in our containers. We cover replace, replace_if, replace_copy, replace_copy_if, remove, remove_if, remove_copy, and remove_copy_if.
DreamShaper_v7_priestess_Sidecut_hair_modest_clothes_fully_clo_0.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved