As far as I know you call the copy constructor in the following cases:
1 When instantiating one object and initializing it with values from another object
2 When pas
Because of Return Value Optimization the 3rd test case's copy constructor call is optimized away by the compiler.
In virtually all cases, the compiler is not allowed to change the meaning of your code. It can (and will when optimising) change your code dramatically from what you wrote into something more optimal whilst never changing the observable behaviour of your code.
This is why when debugging you'll see many confusing things as the generated code does something totally different from what you wrote whilst keeping the observable state intact. This actually makes writing debuggers hard - if for instance you want to examine the value of a variable whilst debugging, the compiler may've decided that the variable didn't even need to exist and so in that case what should the debugger show?
In a very small number of cases the compiler is allowed to change the meaning of your code. The RVO and NRVO [same link as above] are two examples of these - the compiler is allowed to elide the copy constructor in these limited cases - which is what you are seeing. The compiler should still check that your copy constructor exists and is accessible - for instance it isn't private, deleted or impossible to generate - but it can then not use it.
For this reason Copy Constructors [and by inference destructors] should just do the normal thing - as if they're elided you'll get observably different behaviour.