2

Given the following toy code:

class P  // with compiler-generated copy constructor and move constructor
{
public:
    P(int x, int y) { }
};

int main()
{
    P p({x,y});
}

In my current understanding, the {x,y} in P p({x,y}); is converted into an object of type P by implicitly calling the constructor P::P(int x, int y) and passing x and y to it. Usually there is optimization so that this P object is directly constructed as p. Nevertheless, may I ask if this implicit call of P::P(int x, int y) is invoked by the move constructor or the copy constructor (generated by the compiler)?

CPPL
  • 726
  • 1
  • 10
  • you can implement the constructors and add some `std::cout`, then you will see if they are called – 463035818_is_not_an_ai Jun 02 '22 at 13:34
  • @463035818_is_not_a_number I think it would be optimized away? – CPPL Jun 02 '22 at 13:36
  • 2
    optimizations do not alter the observable behavior. There are exceptions for copy elision and stuff, though if it is optimized away then it wont be called and thats what you will see – 463035818_is_not_an_ai Jun 02 '22 at 13:38
  • i mean I dont understand your worries about getting optimized away. You dont want to study what happens in an unoptimized build anyways – 463035818_is_not_an_ai Jun 02 '22 at 13:39
  • If you are using C++17 or higher, you are guaranteed that no temporary is created. maybe dupe of [this](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization) and [this](https://stackoverflow.com/questions/38043319/how-does-guaranteed-copy-elision-work) combined? – NathanOliver Jun 02 '22 at 13:52
  • FWIW, while your question as asked is easily checkable for yourself, a slightly altered question: "Is that optimization **reliable/guaranteed**" is a lot more interesting (even if it has probably been answered already somewhere) –  Jun 02 '22 at 13:53

1 Answers1

2

Usually there is optimization so that this P object is directly constructed as p. Nevertheless, may I ask if this implicit call of P::P(int x, int y) is invoked by the move constructor or the copy constructor

Let's see what happens here with and without optimizations in C++17 and prior to C++17.

Prior C++17

Prior to C++17 there was non-mandatory copy elision, which means when you wrote:

P p({x,y}); //here a temporary of type X will be created and then moved/copied 

In the above statement, prior to C++17 a temporary of type P will be created which will then be copied/moved using the copy/move constructor. That is the temporary created will be used to copy/move construct p. Since in your example, the compiler will implicitly generate a move constructor, it will be used instead of the implicitly generated copy constructor since a temporary prvalue is used to construct p. It is as-if you wrote:

P p(P{x, y}); //equivalent to what happens without optimization in prior C++17

Note that compilers are allowed to elide this copy/move construction. Thus if you want to see which constructors are called then you can make use of the -fno-elide-constructor that will disable this non-mandatory copy elison. The output of the program with -fno-elide-constructors prior to C++17 is:

parameterized ctor
move ctor

Again if you don't supply the -fno-elide-constructors flag then the compiler will elide this move construction and you will see the output:

parameterized ctor

C++17 & Onwards

From C++17 there is mandatory copy elision. This means there will be no creation of temporary involve here. The object x will be directly constructed as-if you wrote:

P p(x, y); //this is equivalent to what you wrote from C++17 due to mandatory copy elison

That is, from C++17 onwards, there will be no creation of temporary of type P. This means, the flag -fno-elide-constructors will have no effect in your example from C++17.

We're guaranteed that there is no call to the copy/move constructor from C++17.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • I'm curious about how the compiler treats `{x,y}` before it selects move/copy constructor. Here is the thing, I know that the temporary created is an rvalue and rvalue is preferred by move constructor, but I think the creation of that temporary happens logically after the selection of the constructor. I think only after a constructor has been chosen, then the temporary can be created according to the argument of that specific constructor. In this regard, I wonder how the compiler is selecting the move constructor by just looking at `{x,y}` before the temporary is created? – CPPL Jun 03 '22 at 02:20
  • @CPPL The thing is that when we write `P p({x,y});` this is direct initialization of a type `P`. That is `{x, y}` appears in a context in which it should be converted to a `P` object. Thus it is as if you wrote `P p(P{x, y});`. I have added the same in my answer. Check out my updated answer. – Jason Jun 03 '22 at 03:01
  • @CPPL The ambiguity error in your modified example(that you gave in your deleted comment) is because now you have 2 ways of creating `P`. One is from 2 ints ctor `P::P(int, int)` and second from `P::P(Q)`. Thus the compiler doesn't know which one to choose and gives the mentioned error. – Jason Jun 03 '22 at 04:04
  • 2
    Sorry if my deleted comments caused any confusion... I think the observation in my deleted comment is wrong (it's coming from the message given by Resharper C++ and can sometimes be misleading). I run the code and it seems that, for things like `P p({x,y});`, the compiler will always construct a `P` object using `{x,y}` in the first place, then seek for constructor. So it is as you said in your answer. Thanks again for your patience :) – CPPL Jun 03 '22 at 04:11
  • @CPPL That's all fine. We love seeing people come here to learn (and I myself have a long way to go for that matter!). – Paul Sanders Jun 03 '22 at 14:39
  • _From C++17 there is mandatory copy elision._ Yes, from C++17 on the standardisation committee finally grasped the nettle and said, in effect, if you write copy / move constructors with observable side-effects then you're on your own (so don't!). And that was a smart move (no pun intended) IMO, since it materially improves the quality of the code that the compiler can produce, in a variety of situations actually, and especially if a class is copyable but not moveable. – Paul Sanders Jun 03 '22 at 14:42
  • @PaulSanders @Anoop Hi, I actually found that initialization like `P p({x, y});` results in some behaviors I don't understand depending on how the compiler uses `{x,y}`. I'll try to collect some typical examples and express myself in another question. Sorry for asking such kind of question many times... After all I think we should avoid things like `P p({x, y});` in real code and try to write code in a more explicit way... – CPPL Jun 04 '22 at 02:11
  • @CPPL Sure, feel free to ask another question. – Jason Jun 04 '22 at 03:25
  • @PaulSanders and Anoop [Here](https://stackoverflow.com/q/72497394/17172007) is the post. – CPPL Jun 04 '22 at 05:50