Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

pass by value mysteries

134 views
Skip to first unread message

crusad...@gmail.com

unread,
Sep 23, 2016, 7:20:50 AM9/23/16
to
Hi,

It occurred to me that I don't really know exactly how values are passed into functions (and I am using C++ for almost 2 decades). Simple rule of 'never pass anything non-trivial by value' always worked for me. But recently I stumbled upon few articles which claim that pass-by-value makes sense if function being called is going to make a copy anyway.

So, exactly what happens on stack when you have some non-trivial expression, like F f = f(g1(...), g2(...))? After thinking a bit I came to conclusion that if I was a compiler writer -- I'd implement it in such was that values returned by g1() and g2() were created in locations where f() expects them to be. So, basically, no copy constructors will get called -- f will simply take ownership of object constructed by g1 and g2 and destroy them when execution leaves f's scope.

I have a great trust in people behind C++ compilers, but just in case I've decided to check it out. Here is a very simple code:


#include <cstdio>

using std::printf;

struct H21
{
H21() { printf("H21 ctor\n"); }
~H21() { printf("H21 dtor\n"); }
H21(H21 const&) {}
};

H21 h21() { return H21(); }

struct H22
{
H22() { printf("H22 ctor\n"); }
~H22() { printf("H22 dtor\n"); }
H22(H22 const&) {}
};

H22 h22() { return H22(); }

struct G2
{
G2() { printf("G2 ctor\n"); }
~G2() { printf("G2 dtor\n"); }
G2(G2 const&) {}
};

G2 g2(H21, H22) { return G2(); }

struct H11
{
H11() { printf("H11 ctor\n"); }
~H11() { printf("H11 dtor\n"); }
H11(H11 const&) {}
};

H11 h11() { return H11(); }

struct H12
{
H12() { printf("H12 ctor\n"); }
~H12() { printf("H12 dtor\n"); }
H12(H12 const&) {}
};

H12 h12() { return H12(); }

struct G1
{
G1() { printf("G1 ctor\n"); }
~G1() { printf("G1 dtor\n"); }
G1(G1 const&) {}
};

G1 g1(H11, H12) { return G1(); }

struct F
{
F() { printf("F ctor\n"); }
~F() { printf("F dtor\n"); }
};

F f(G1, G2) { return F(); }

int main()
{
F v = f(g1(h11(), h12()), g2(h21(), h22()));
return 0;
}

If you compile and run it in MSVC 2015 it'll produce exactly what I expect it to:

H22 ctor
H21 ctor
G2 ctor
H21 dtor
H22 dtor
H12 ctor
H11 ctor
G1 ctor
H11 dtor
H12 dtor
F ctor
G1 dtor
G2 dtor
F dtor


Now. I would not be posting this if everything was according to my expectations, right? :-)

If you comment out every copy constructor, output changes to this:

H22 ctor
H21 ctor
G2 ctor
H21 dtor
H22 dtor
H12 ctor
H11 ctor
G1 ctor
H11 dtor
H12 dtor
F ctor
G1 dtor
G2 dtor
G1 dtor <-- next 6 lines are totally unexpected
H11 dtor
H12 dtor
G2 dtor
H21 dtor
H22 dtor
F dtor

Now this is totally unexpected. For some reason compiler decided to make few extra copies (by means of either copy ctor or move ctor). I made these objects real big (added int v[1000]) -- it changed nothing.

Question #1: Why? What is going on here?


I tried this code with GCC 5.4.0 and it's output doesn't make any sense at all (though I am surely glad it doesn't create extra copies):

H22 ctor
H21 ctor
G2 ctor
H12 ctor
H11 ctor
G1 ctor
F ctor
G1 dtor
H11 dtor
H12 dtor
G2 dtor
H21 dtor
H22 dtor
F dtor

hiding copy constructors has no effect on GCC.

Question #2: Why GCC output looks like this? What is going on here?

Question #3: What happened to comp.lang.c++.moderated in Google Groups? It shows last message from May 22 for me.

Regards,
Michael.

Paavo Helde

unread,
Sep 23, 2016, 8:26:05 AM9/23/16
to
On 23.09.2016 14:34, Stefan Ram wrote:
> crusad...@gmail.com writes:
>> So, exactly what happens on stack when you have some
>
> There is no stack. C++ is not assembler.
>
>> For some reason compiler decided to make few extra copies
>
> You seem to have written a user-defined constructor.
> That might have kept the compiler from generating
> a move constructor.

Normal user-defined constructor does not affect generation of implicit
move constructor (user-defined copy constructors, copy assignments, move
assignments and destructors do).

>
>> Question #3: What happened to comp.lang.c++.moderated in
>> Google Groups? It shows last message from May 22 for me.
>
> When I posted there, they modified my posts (changed
> indentations)

Really? Only indendantion? If I were a moderator, I would certainly
change the ::std:: prefixes as well.

Cheers
Paavo




Alf P. Steinbach

unread,
Sep 23, 2016, 12:37:21 PM9/23/16
to
At the end it was my impression that it was only Victor Bazarov holding
the fort, and he posted here in clc++ about the technical problems.

Essentially the tech problems began years ago when the servers we used
at a university in Australia, I think it was, were shut down. Daveed
found alternative free hosting. But now, recently, also that stopped
working.

I was unable to help much after I got really ill and then went through a
year or two of surgery. After that I didn't really get back into it,
though with some effort and iron will, which I lack, I probably could
have. And the popularity of a group like clc++m depends much on prompt
moderation so that one gets interesting back-and-forth exchanges.

In short, clc++m is apparently dead.

I'm sorry.


- Alf (inactive mod)

Mr Flibble

unread,
Sep 23, 2016, 9:35:31 PM9/23/16
to
On 23/09/2016 12:34, Stefan Ram wrote:
> crusad...@gmail.com writes:
>> So, exactly what happens on stack when you have some
>
> There is no stack. C++ is not assembler.

What difference does whether it is C++ or assembler make to whether or
not there is a stack?

Of course there is a fucking stack you fucking mentalist; the C++ ISO
Standard even mentions it.

/Flibble

Jerry Stuckle

unread,
Sep 23, 2016, 10:53:54 PM9/23/16
to
The stack in the C++ standard is not necessarily a hardware stack.
Otherwise you could not compile and run C++ (or C, for that matter)
programs in IBM mainframes - which have no hardware stack.

The stack in the C++ standard is a concept. It can be implemented as a
hardware stack, a chain of save areas or any other similar means.

--
==================
Remove the "x" from my email address
Jerry Stuckle
jstu...@attglobal.net
==================

Michael Kilburn

unread,
Sep 23, 2016, 11:01:37 PM9/23/16
to
On Friday, September 23, 2016 at 11:37:21 AM UTC-5, Alf P. Steinbach wrote:
> On 23.09.2016 14:25, Paavo Helde wrote:
> > On 23.09.2016 14:34, Stefan Ram wrote:

For some reason Stefan Ram's post doesn't show up in Google Groups... Can someone repost it (only if it contains correct answers to Q #1 and #2)?

Overall I see 4 reply posts in Groups and none of them answer main questions. Regarding stack -- the fact that there is a hardware where stack isn't really a stack (even though it is essentially a stack) does not justify inefficient (or incorrect) behavior of code generated for IA-32 :-)


> In short, clc++m is apparently dead.

Shame

Melzzzzz

unread,
Sep 23, 2016, 11:56:09 PM9/23/16
to
Your question does not have to do anything with stack... Object passed by
value are usually constructed at the caller site and destructed there, while
hidden pointers to them are passed to functions.

How many objects will be constructed does not depends at all if stack is
used (or not)...

You have to prepare for worst case always...

>
>

--
press any key to continue or any other to quit

Paavo Helde

unread,
Sep 24, 2016, 11:35:16 AM9/24/16
to
FYI: there were no answers in Stefan Ram's post, just some nitpicking
over "stack" and a remark that an implicitly defined move constructor
might have some role here (which I think is not the case because
presence of an explicit destructor prohibits creation of an implicit
move constructor).

As the context is snipped away here, I will try to answer the OP
questions in another reply.

Cheers
Paavo

woodb...@gmail.com

unread,
Sep 24, 2016, 1:22:01 PM9/24/16
to
I'm thankful for the freedom in this newsgroup to use the
:: prefixes. I rarely used c.l.c++.m so this apparent fading
away isn't a big deal to me. It may even be beneficial in
that some people from that newsgroup may be added to this
newsgroup, which IMO is easier to deal with than c.l.c++.m
was.

Brian
Ebenezer Enterprises - In G-d we trust.
http://webEbenezer.net

woodb...@gmail.com

unread,
Sep 24, 2016, 1:26:16 PM9/24/16
to
On Friday, September 23, 2016 at 8:35:31 PM UTC-5, Mr Flibble wrote:
> On 23/09/2016 12:34, Stefan Ram wrote:
> > crusad...@gmail.com writes:
> >> So, exactly what happens on stack when you have some
> >
> > There is no stack. C++ is not assembler.
>
> What difference does whether it is C++ or assembler make to whether or
> not there is a stack?

Leigh, please don't swear here.

Brian
Ebenezer Enterprises

Paavo Helde

unread,
Sep 24, 2016, 2:12:37 PM9/24/16
to
As the classes contain no data, I guess the compiler decided to create
some copies "as if" via copy constructor (which is a null-op, so zero
cost). If you put some content in G1, G2, e.g. another class object with
printouts in ctor, dtor, copy ctor, move ctor, then the behavior goes
back to the previous (less objects created-destroyed).

In general, if you want to avoid unneeded copies in situations like
here, define proper move (rvalue) constructors for your classes. Relying
on the compiler to optimize the copies away is not guaranteed (though
might probably work in simple scenarios).

>
> I tried this code with GCC 5.4.0 and it's output doesn't make any sense at all (though I am surely glad it doesn't create extra copies):
>
> H22 ctor
> H21 ctor
> G2 ctor
> H12 ctor
> H11 ctor
> G1 ctor
> F ctor
> G1 dtor
> H11 dtor
> H12 dtor
> G2 dtor
> H21 dtor
> H22 dtor
> F dtor
>
> hiding copy constructors has no effect on GCC.
>
> Question #2: Why GCC output looks like this? What is going on here?

What do you mean? As far as I can see there is exactly 1 ctor and 1 dtor
call for each object. All objects other than F are destroyed before the
semicolon is reached in the first line of main. What do you think is
wrong here?


> Question #3: What happened to comp.lang.c++.moderated in Google Groups? It shows last message from May 22 for me.

No idea, I stopped to use it many years ago because of unbearable
response loop times.

Cheers
Paavo


Mr Flibble

unread,
Sep 24, 2016, 3:15:42 PM9/24/16
to
'This video may contain sexual swearwords, I'm afraid. There are 28
'fucks'. Including that one 29. Ah, fuck it, make it 30.' -- Paul Calf

https://www.youtube.com/watch?v=42UCpOzTbNU

/Flibble

Chris Vine

unread,
Sep 24, 2016, 7:10:08 PM9/24/16
to
On Sat, 24 Sep 2016 10:21:51 -0700 (PDT)
woodb...@gmail.com wrote:
[snip]
> I'm thankful for the freedom in this newsgroup to use the
> :: prefixes. I rarely used c.l.c++.m so this apparent fading
> away isn't a big deal to me. It may even be beneficial in
> that some people from that newsgroup may be added to this
> newsgroup, which IMO is easier to deal with than c.l.c++.m
> was.

Well I hope c.l.c++.m doesn't go because it may well be needed shortly.
Religious nutjobs like you and Rick, who can't control themselves and
have no concern for the supposed topic of this newsgroup are in danger
of making it unusable for serious discussion of C++.

At least the nonsense that Rick and you come up with wouldn't get past
the moderator on c.l.c++.m.

While I am on the subject of nutjobs, you are free to prefix your use
of the std namespace with the :: global scope operator with or without
this newsgroup and any supposed freedom for which you are thankful.
You comment on this above is bizarre enough as it stands. But holding
out the :: prefix as an attribute of a "royal priesthood" as in your
recent post? Please... you are raving.

Michael Kilburn

unread,
Sep 24, 2016, 7:30:03 PM9/24/16
to
On Saturday, September 24, 2016 at 1:12:37 PM UTC-5, Paavo Helde wrote:
> > Question #1: Why? What is going on here?
>
> As the classes contain no data, I guess the compiler decided to create
> some copies "as if" via copy constructor (which is a null-op, so zero
> cost). If you put some content in G1, G2, e.g. another class object with
> printouts in ctor, dtor, copy ctor, move ctor, then the behavior goes
> back to the previous (less objects created-destroyed).

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.


> In general, if you want to avoid unneeded copies in situations like
> here, define proper move (rvalue) constructors for your classes. Relying
> on the compiler to optimize the copies away is not guaranteed (though
> might probably work in simple scenarios).

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?).


> > Question #2: Why GCC output looks like this? What is going on here?
>
> What do you mean? As far as I can see there is exactly 1 ctor and 1 dtor
> call for each object. All objects other than F are destroyed before the
> semicolon is reached in the first line of main. What do you think is
> wrong here?

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.

woodb...@gmail.com

unread,
Sep 24, 2016, 9:05:21 PM9/24/16
to
On Saturday, September 24, 2016 at 6:10:08 PM UTC-5, Chris Vine wrote:
> On Sat, 24 Sep 2016 10:21:51 -0700 (PDT)
> woodb...@gmail.com wrote:
> [snip]
> > I'm thankful for the freedom in this newsgroup to use the
> > :: prefixes. I rarely used c.l.c++.m so this apparent fading
> > away isn't a big deal to me. It may even be beneficial in
> > that some people from that newsgroup may be added to this
> > newsgroup, which IMO is easier to deal with than c.l.c++.m
> > was.
>
> Well I hope c.l.c++.m doesn't go because it may well be needed shortly.
> Religious nutjobs like you and Rick, who can't control themselves and
> have no concern for the supposed topic of this newsgroup are in danger
> of making it unusable for serious discussion of C++.
>

I see a difference between Rick and myself. He often starts
threads that are not about C++. If he did it a few times a
year it wouldn't be so bad, but he's doing it on a weekly
basis if not more so.

I just let people know that the blessings I have (starting
a company, using C++ heavily in the company, focusing on on-line
code generation, etc.) are from G-d.

> At least the nonsense that Rick and you come up with wouldn't get past
> the moderator on c.l.c++.m.

I don't read a lot of what Rick writes here. What I have
read, I sometimes agree with him, but not always.

>
> While I am on the subject of nutjobs, you are free to prefix your use
> of the std namespace with the :: global scope operator with or without
> this newsgroup and any supposed freedom for which you are thankful.
> You comment on this above is bizarre enough as it stands. But holding
> out the :: prefix as an attribute of a "royal priesthood" as in your
> recent post?

I believe that.

> Please... you are raving.

Let's see. I foresaw the ongoing financial collapses and
realized the need to start a company. I stuck with C++ when
some thought it was dying. I've worked on an on line approach
that has many advantages over things like the serialization
library in Boost, Protocol Buffers, etc. As far as I can tell
I'm like Ben Shapiro -- http://dailywire.com -- an example of
American exceptionalism -- blessed because of my faith.

Brian
Ebenezer Enterprises
http://webEbenezer.net

Chris M. Thomasson

unread,
Sep 24, 2016, 10:57:38 PM9/24/16
to
On 9/24/2016 6:05 PM, woodb...@gmail.com wrote:
[...] As far as I can tell
> I'm like Ben Shapiro -- http://dailywire.com -- an example of
> American exceptionalism -- blessed because of my faith.

What does this actually mean?

Ian Collins

unread,
Sep 24, 2016, 11:02:54 PM9/24/16
to
Delusions of grandeur?

--
Ian

Chris M. Thomasson

unread,
Sep 24, 2016, 11:38:07 PM9/24/16
to
Yikes! I hope this type of grandeur is not as extreme as the Black
Knight in MP...

https://youtu.be/mjEcj8KpuJw

;^)



Paavo Helde

unread,
Sep 25, 2016, 4:51:37 AM9/25/16
to
On 25.09.2016 2:28, Michael Kilburn wrote:
> On Saturday, September 24, 2016 at 1:12:37 PM UTC-5, Paavo Helde wrote:
>>> Question #1: Why? What is going on here?
>>
>> As the classes contain no data, I guess the compiler decided to create
>> some copies "as if" via copy constructor (which is a null-op, so zero
>> cost). If you put some content in G1, G2, e.g. another class object with
>> printouts in ctor, dtor, copy ctor, move ctor, then the behavior goes
>> back to the previous (less objects created-destroyed).
>
> 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?

>
>> In general, if you want to avoid unneeded copies in situations like
>> here, define proper move (rvalue) constructors for your classes. Relying
>> on the compiler to optimize the copies away is not guaranteed (though
>> might probably work in simple scenarios).
>
> 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.

>
>>> Question #2: Why GCC output looks like this? What is going on here?
>>
>> What do you mean? As far as I can see there is exactly 1 ctor and 1 dtor
>> call for each object. All objects other than F are destroyed before the
>> semicolon is reached in the first line of main. What do you think is
>> wrong here?
>
> 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().

If you look at the gcc output then you see that it destructs objects in
the exact opposite order of construction, which actually makes a lot of
sense.

Cheers
Paavo

Alf P. Steinbach

unread,
Sep 25, 2016, 9:52:58 AM9/25/16
to
On 25.09.2016 10:51, Paavo Helde wrote:
>
> If you look at the gcc output then you see that it destructs objects in
> the exact opposite order of construction, which actually makes a lot of
> sense.


C++11 §12.2/5:

“The destruction of a temporary whose lifetime is not extended by being
bound to a reference is sequenced before the destruction of every
temporary which is constructed earlier in the same full-expression”


Cheers!,

- Alf
(Oh, it's Sunday)


Paavo Helde

unread,
Sep 25, 2016, 10:49:52 AM9/25/16
to
Thanks for pointing this out! Does this mean that MSVC is just
non-conforming in this regard?

Cheers
Paavo

Jerry Stuckle

unread,
Sep 25, 2016, 11:58:57 AM9/25/16
to
But it doesn't say what the order of construction is. Just that
temporaries are destructed in the reverse order of construction - as
Paavo said.

Scott Lurndal

unread,
Sep 25, 2016, 12:12:31 PM9/25/16
to
Ian Collins <ian-...@hotmail.com> writes:
> Thunderbird/38.5.1
>In-Reply-To: <ns7eee$hig$1...@dont-email.me>
>X-Received-Body-CRC: 3900057631
>X-Received-Bytes: 1456
No, just simple insanity.

Michael Kilburn

unread,
Sep 26, 2016, 8:03:38 AM9/26/16
to
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.


> > 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.


> > 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:

"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.

Not sure that C++11 has changed this... That is why I am seeking help.


> If you look at the gcc output then you see that it destructs objects in
> the exact opposite order of construction, which actually makes a lot of
> sense.

Yes, it does. But what doesn't make any sense is why the same code generates different observable behavior when compiled by two top-level compilers. And it doesn't seem to me as one of those cases where standard permits it. But I might be wrong, of course. :-\


Regards,
Michael.

Paavo Helde

unread,
Sep 26, 2016, 9:33:55 AM9/26/16
to
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

Michael Kilburn

unread,
Sep 26, 2016, 5:49:16 PM9/26/16
to
On Monday, September 26, 2016 at 8:33:55 AM UTC-5, Paavo Helde wrote:
> 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.

I have asked for help to understand what is going on here and why.



> > 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).

I was under impression that copying (when passing value as an argument) needs to happen only if you pass an lvalue (or value need to be converted to argument's type). The whole idea behind rvalues is that compiler can control memory address at which they'll get created (thus allowing it to be passed "into" function scope without any extra copy). Surprisingly, I can't find anything concrete in C++ standard on this topic -- hence (after 19 years of C++ experience) I am totally surprised that such trivial matter doesn't have a clear explanation.


> 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).

This doesn't prove anything -- this rule could've been created to make sure lvalues and rvalues are treated uniformly when passed as function argument.


> 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.

We clearly aren't on the same page wrt what "passing an argument to a function" means. In my understanding to "pass" you need to create a value (at certain location) and call a function (which takes ownership of the value and will destroy it on exit) -- which means that yes, value created outside(!) of the function will be destroyed inside(!) of it.


> > "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.

I looked at both standards once again -- can't find anything that would allow this "optimization" -- to my knowledge both a1 and a2 needs to be destroyed before we return from g2. In this regard MSVC is correct and GCC (as I said before) "doesn't make any sense". Hence, my question -- what happens here? Is it allowed under C++ standard?


> 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.

I don't think we could make any progress here without defining what "to pass a value" is. If there is (semantically) another copy that needs to be created before we call a function -- that copy have the same problem as value returned from h11() -- it has to be destroyed after G2 is destroyed, and yet it also has to be destroyed on exit from g2(). Sometimes C++ is simply killing me with cr.p like this... :-\


> 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.

Hmm... So, you are basically in my shoes -- you don't understand WTH is happening here :-) . Well, I am glad not to be alone

Regards,
Michael.
0 new messages