On 26.09.2016 15:03, Michael Kilburn wrote:
> On Sunday, September 25, 2016 at 3:51:37 AM UTC-5, Paavo Helde wrote:
>> On 25.09.2016 2:28, Michael Kilburn wrote:
>>> On Saturday, September 24, 2016 at 1:12:37 PM UTC-5, Paavo Helde wrote:
>>> Try adding POD class members -- regardless how big they are compiler will still create new copies. Basically, anything that changes class to non-POD "fixes" the issue.
>>
>> Then maybe this is a quality-of-implementation issue of the Microsoft
>> compiler? Maybe they are trying to keep some backward-compatibility
>> behavior for old legacy C programs?
>
> Highly doubt it. I think it simply prefers move ctor "too much". If you add anything that stops it's auto-generation -- it works as it is supposed to. In any case -- looks like a bug to me. I kinda hoped to find help on Usenet (as I was usually able to ages ago), but apparently Google finally killed it.
But you have not asked for any help so far, only wondered about "what
happens", and I have tried to explain what I think what happens. If you
need any related help, like for example how to avoid excessive copying
of huge data arrays, then I'm sure several people would chime in (my
answer would be: use std::vector and move constructors (implicit or
explicit)).
>
>>> I don't think it is about optimizations... In f(g()) expression of g's return type is exact match of f's argument type -- I expect no additional copies (it simply doesn't make any sense -- why create a copy of a temporary if g already created a temporary we need?).
>>
>> What one expects does not change the reality :-) ... unfortunately. And
>> the reality is that such optimizations are not mandated and apparently
>> not universally supported.
>
> I believe it has nothing to do with optimizations -- passing rvalue (value returned by a function) as an argument to another function should not involve any copying.
I think this has everything to do with optimizations. Passing objects
around by value involves copies at each step formally, and avoiding such
copies is an optimization (sometimes harder, sometimes easier, sometimes
even hard to avoid for the compiler, but an optimization nevertheless).
You can easily test that, declare a private or deleted copy ctor and
everything which formally requires copies stops working (at least with
conformant compilers like recent gcc or clang).
>
>>> Order is wrong. Everywhere. "G2 g2(H21, H22) { return G2(); }" means that soon after G2' ctor we are supposed to get H21 and H22 dtor calls (because we leave g2's scope). Same for every function call in this example.
>>
>> The C++ standard does not contain any such phrase like "soon after". The
>> order of any operations between sequence points is not fixed, so an
>> implementation can do them in whatever order they like. The standard
>> just requires that all the operations must be completed before the next
>> sequence point, which in this case is the semicolon in the end of the
>> first line of main().
>
> First, there are no 'sequence points' in C++11 and higher. Second, check 1.9.p17 in C++03 standard -- there are two extra sequence points on entry and exit from function call. This basically means that in C++03 in this function:
Right, I need to update my vocabulary.
Yes, there are sequence points or equivalent at entry and exit from the
function call, but this does not mean that some temporary created before
the function call should suddenly be destroyed inside this function.
> "G2 g2(H21 a1, H22 a2) { return G2(); }"
>
> after G2 ctor a1 and a2 should be destroyed before anything else outside of the function can happen.
If there were no optimizations and a1 and a2 were really the function
parameters, then indeed they should be destroyed before the function
exit. However, all this thread is about optimizations which allow the
temporaries created outside of the function to be merged with a1, a2
without any copies. To remind, the full expression was:
F v = f(g1(h11(), h12()), g2(h21(), h22()));
And as Alf reminded us, there is a rule that all temporaries created in
a full expression must be destroyed in exact reverse order of creation
before the end of the full expression.
Here, H21 and G2 objects are for example clearly temporaries created in
this full expression. H21 naturally needs to be created before G2, so
this rule says it must be destroyed *after* G2, which means there is no
way it could be destroyed "inside" g2() call.
Yes, this seems to contradict the rule that function parameters are
destroyed upon function exit.
Without optimizations everything would be clear, function parameter
would be a copy of the temporary and properly destroyed before function
exit. But the optimization removed the copy, so there are not two
objects any more (a temporary and a parameter), but only one. To me it
seems now that MSVC thinks that the remaining object is the parameter,
and the temporary has been optimized away, while gcc thinks that the
remaining object is the temporary and the parameter has been optimized away.
I'm not sure who is right here, maybe they both are. A practical insight
from this is that one should not write code which depends on the exact
order of destructor calls in such situations. The order of construction
of leaf Hxx objects is arbitrary anyway (they could be all created
before calling any of g1(), g2()), so there is not much lost here.
Cheers,
Paavo