My references are to std::copy and std::copy_backward.
template< class InputIt, class OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt d_first );
Copies all elements in the range [first, last) starting from first and proceeding to last - 1. The behavior is undefined if d_first is within the range [first, last). In this case, std::copy_backward may be used instead.
template< class BidirIt1, class BidirIt2 > BidirIt2 copy_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last )
Copies the elements from the range, defined by [first, last), to another range ending at d_last. The elements are copied in reverse order (the last element is copied first), but their relative order is preserved.
The behavior is undefined if d_last is within (first, last]. std::copy must be used instead of std::copy_backward in that case.
When copying overlapping ranges, std::copy is appropriate when copying to the left (beginning of the destination range is outside the source range) while std::copy_backward is appropriate when copying to the right (end of the destination range is outside the source range).
From the above description, I gather the following inference:
Both copy and copy_backward end up copying the same source range [first, last) to the destination range, albeit in the case of the former the copying occurs from first to last - 1, whereas in the case of the latter the copying occurs from last -1 to first. In both cases, relative order of elements in the source range is preserved in the resulting destination range.
However, what is the technical reason behind the following two stipulations:
1) In the case of copy, undefined behavior results (implying unsuccessful copying of the source range to the destination range and possibly system fault) if d_first is within the range [first, last).
2) In the case of copy_backward, undefined behavior results (implying unsuccessful copying of the source range to the destination range and possibly system fault) if d_last is within the range (first, last].
I am assuming that the suggestion to replace copy with copy_backward to avert the above undefined behavior scenario, would become evident to me once I understand the implication of the above two statements.
Likewise, I am also assuming that the mention about the appropriateness of copy when copying to the left (which notion is not clear to me), and copy_backward when copying to the right (which notion is not clear to me either), would begin to make sense once I comprehend the above distinction between copy and copy_backward.
Look forward to your helpful thoughts as always.
As a follow-up, I wrote the following test code to verify the behavior of both copy and copy_backward, for identical operation.
#include <array>
#include <algorithm>
#include <cstddef>
#include <iostream>
using std::array;
using std::copy;
using std::copy_backward;
using std::size_t;
using std::cout;
using std::endl;
int main (void)
{
const size_t sz = 4;
array<int,sz>a1 = {0,1,2,3};
array<int,sz>a2 = {0,1,2,3};
cout << "Array1 before copy" << endl;
cout << "==================" << endl;
for(auto&& i : a1) //the type of i is int&
{
cout << i << endl;
}
copy(a1.begin(),a1.begin()+3,a1.begin()+1);
cout << "Array1 after copy" << endl;
cout << "=================" << endl;
for(auto&& i : a1) //the type of i is int&
{
cout << i << endl;
}
cout << "Array2 before copy backward" << endl;
cout << "===========================" << endl;
for(auto&& i : a2) //the type of i is int&
{
cout << i << endl;
}
copy_backward(a2.begin(),a2.begin()+3,a2.begin()+1);
cout << "Array2 after copy backward" << endl;
cout << "==========================" << endl;
for(auto&& i : a2) //the type of i is int&
{
cout << i << endl;
}
return (0);
}
The following is the program output:
Array1 before copy
==================
0
1
2
3
Array1 after copy
=================
0
0
1
2
Array2 before copy backward
===========================
0
1
2
3
Array2 after copy backward
==========================
2
1
2
3
Evidently, copy produces the expected result, whereas copy_backward doesn't, even though d_first is within the range [first, last). Additionally, d_last is within the range (first, last] as well, which should result in undefined behavior in the case of copy_backward as per the documentation.
So in effect, the program output is in accordance with the documentation in the case of copy_backward, whereas it is not in the case of copy.
It is worth noting again that in both cases, d_first and d_last do satisfy the condition which should result in undefined behavior for both copy and copy_backward respectively, as per documentation. However, the undefined behavior is observed only in the case of copy_backward.
There is nothing deep going on here. Just do an algorithm run-through with sample data using a naive approach: copy each element in order.
Suppose you have the four-element array int a[4] = {0, 1, 2, 3}
and you want to copy the first three elements to the last three. Ideally, you would end up with {0, 0, 1, 2}
. How would this (not) work with std::copy(a, a+3, a+1)
?
Step 1: Copy the first element a[1] = a[0];
The array is now {0, 0, 2, 3}
.
Step 2: Copy the second element a[2] = a[1];
The array is now {0, 0, 0, 3}
.
Step 3: Copy the third element a[3] = a[2];
The array is now {0, 0, 0, 0}
.
The result is wrong because you overwrote some of your source data (a[1]
and a[2]
) before reading those values. Copying in reverse would work because in reverse order, you would read values before overwriting them.
Since the result is wrong with one reasonable approach, the standard declared the behavior "undefined". Compilers wishing to take the naive approach may, and they do not have to account for this case. It is OK to be wrong in this case. Compilers that take a different approach might produce different results, maybe even the "correct" results. That is also OK. Whatever is easiest for the compiler is fine by the standard.
In light of the question's addendum: please note that this is undefined behavior. That does not mean the behavior is defined to be contrary to the programmer's intent. Rather, it means that the behavior is not defined by the C++ standard. It is up to each compiler to decide what happens. The result of std::copy(a, a+3, a+1)
could be anything. You might get the naive result of {0, 0, 0, 0}
. However, you might instead get the intended result of {0, 0, 1, 2}
. Other results are also possible. You cannot conclude that there is no undefined behavior simply because you were lucky enough to get the behavior you intended. Sometimes undefined behavior gives correct results. (That's one reason that tracking down bugs related to undefined behavior can be so difficult.)