copy
, copy_n
, copy_if
, copy_backward
, reverse_copy
, rotate_copy
, and unique_copy
. 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 rangeout
: 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())};
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:
#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 rangeout
- 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 rangeout
: 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 rangeout
- 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 rangeout
- 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:
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 rangeout
- 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 rangeout
- 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
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.