In my answer, I'd like to address the question title literally:
How to know when a copy is being created?
This can be achieved quite easy with a sample class where every constructor in quest makes an output:
struct Data {
Data() { std::cout << "Data::Data()\n"; }
Data(const Data&) { std::cout << "Data::Data(const Data&)\n"; }
Data& operator=(const Data&) { std::cout << "Data::operator=(const Data&)\n"; return *this; }
Data(Data&&) { std::cout << "Data::Data(Data&&)\n"; }
Data& operator=(Data&&) { std::cout << "Data::operator=(Data&&)\n"; return *this; }
};
According to cppreference.com, the copy constructor of std::pair is the default, i.e. it just calls the copy constructors of its member variables:
pair( const pair& p ) = default;
Thus, a little MCVE can illustrate the mistake of the OP quite obviously:
#include <iostream>
#include <map>
struct Data {
Data() { std::cout << "Data::Data()\n"; }
Data(const Data&) { std::cout << "Data::Data(const Data&)\n"; }
Data& operator=(const Data&) { std::cout << "Data::operator=(const Data&)\n"; return *this; }
Data(Data&&) { std::cout << "Data::Data(Data&&)\n"; }
Data& operator=(Data&&) { std::cout << "Data::operator=(Data&&)\n"; return *this; }
};
#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ ; std::cout << std::endl
int main()
{
DEBUG(std::map<int, Data> aMap = { { 1, Data() }, { 2, Data() } });
// done like OP -> copy assignment
DEBUG(for (const std::pair<int, Data>& value : aMap) {
std::cout << value.first << '\n';
});
// without copy assignment
DEBUG(for (const std::pair<const int, Data>& value : aMap) {
std::cout << value.first << '\n';
});
DEBUG(for (const std::map<int, Data>::value_type& value : aMap) {
std::cout << value.first << '\n';
});
}
Output:
std::map<int, Data> aMap = { { 1, Data() }, { 2, Data() } };
Data::Data()
Data::Data(Data&&)
Data::Data()
Data::Data(Data&&)
Data::Data(const Data&)
Data::Data(const Data&)
for (const std::pair<int, Data>& value : aMap) { std::cout << value.first << '\n'; };
Data::Data(const Data&)
1
Data::Data(const Data&)
2
for (const std::pair<const int, Data>& value : aMap) { std::cout << value.first << '\n'; };
1
2
for (const std::map<int, Data>::value_type& value : aMap) { std::cout << value.first << '\n'; };
1
2
Demo on coliru
Thus, for the first loop (using std::pair<int, Data>) the copy constructor of Data is called in each iteration.
This doesn't happen for std::pair<const int, Data>
nor for the std::map<int, Data>::value_type.
Another simple alternative could have been the usage of auto:
for (const auto& value : aMap) {
std::cout << value.first << '\n';
}
Furthermore, I would like to mention the diagnostic of g++ with an even shorter MCVE:
#include <iostream>
#include <map>
struct Data { };
int main()
{
std::map<int, Data> aMap = { { 1, Data() } };
for (const std::pair<int, Data>& value : aMap) std::cout << value.first << '\n';
}
Output:
main.cpp: In function 'int main()':
main.cpp:9:36: warning: loop variable 'value' of type 'const std::pair<int, Data>&' binds to a temporary constructed from type 'std::pair<const int, Data>' [-Wrange-loop-construct]
9 | for (const std::pair<int, Data>& value : aMap) std::cout << value.first << '\n';
| ^~~~~
main.cpp:9:36: note: use non-reference type 'const std::pair<int, Data>' to make the copy explicit or 'const std::pair<const int, Data>&' to prevent copying
Demo on coliru