I am trying to understand the concept of copy and move constructors in C . And so trying out different examples. One such example whose output i am unable to understand is given below:
#include <iostream>
#include <vector>
using namespace std;
struct NAME
{
NAME()
{
std::cout<<"default"<<std::endl;
}
NAME(const NAME& )
{
std::cout<<"const copy"<<std::endl;
}
NAME(NAME& )
{
std::cout<<"nonconst copy"<<std::endl;
}
NAME(NAME &&)
{
std::cout<<"move"<<std::endl;
}
};
void foo(std::pair<std::string, NAME> )
{
}
void foo2(std::vector<std::pair<std::string, NAME>> )
{
}
int main()
{
foo(std::make_pair("an", NAME())); //prints default --> move --> move
std::cout << "----------------------------------------"<<std::endl;
foo({"an", NAME()}); //prints default --> move
std::cout << "----------------------------------------"<<std::endl;
foo2({{"an", NAME()}}); //prints default --> move --> const copy
std::cout << "----------------------------------------"<<std::endl;
return 0;
}
Case 1: For foo(std::make_pair("an", NAME()));
Output
default
move
move
This is what i think is happening.
Step 1. A temporary of type NAME() is craeted because we've passed it to std::make_pair
using the NAME()
's default constructor.
Step 2. One of the std::make_pair
's constructor is used which forwards(moves) the the temporary that was created in the 1st step. So NAME()
's move constructor.
Step 3. Finally, since the argument to foo
is passed by value, so the std::pair
that was created in step 2 is "moved"(not "copied"?) which in turn "moves" the NAME
temporary one last time.
Case 2: For foo({"an", NAME()});
Output
default
move
Step 1. A temporary NAME
is created.
Step 2. This time since we do not have std::make_pair
, std::pair
's initializer list constructor(if any) is used to "move" the temporary created in step 1.
Case 3: For foo2({{"an", NAME()}});
Output
default
move
const copy
I have no idea why copy constructor is used instead of move constructor and also why the const
version is used instead of nonconst version of copy constructor.
Are my explanation correct. Please correct me where i am wrong in any of my explanation steps in detail.
CodePudding user response:
Case 1: For
foo(std::make_pair("an", NAME()));
Output
default move move
The crucial issue here is to understand what is the type of the above make_pair
invocation. It is not a std::pair<std::string, NAME>
as I think you believe, but a std::pair<const char *, NAME>
!
You can confirm this by checking that this prints 1
.
std::cout << std::is_same_v< decltype(std::make_pair("an", NAME()))
, std::pair<const char *, NAME>> << "\n";
So, we observe that:
NAME
is constructed using the default constructor. (output:default
)make_pair
moves that to return its pair (output:move
)- We can not pass that pair to
foo
, which instead expectsstd::pair<std::string, NAME>
. An implicitpair
conversion constructor is called, which creates thestring
and moves theNAME
once again. (output:move
)
This is why we observe two moves instead of just one.
Case 2: For
foo({"an", NAME()});
Output
default move
Here we don't call a function to make the pair, so "an"
is used to initialize the needed pair directly, which has the right std::pair<std::string, NAME>
. We no longer create a pair with the "wrong" type, hence one fewer move.
On case 3: I could not understand why the move constructor is called in case 3. I thought that in order to allow vector
to move the NAME
, it would have sufficed to mark the move constructor as noexcept
, but even in that case the copy is performed. I have no idea right now.
It looks like the move constructor of std::pair
is not (conditionally) noexcept
as one would expect. That might be the underlying cause.
CodePudding user response:
Elements of std::initializer_list
, are const
and cannot be moved from.