Topic: lvalues, rvalues and reference lifetime


Author: jpotter@falcon.lhup.edu (John Potter)
Date: Wed, 30 Jul 2003 23:48:34 +0000 (UTC)
Raw View
On Tue, 29 Jul 2003 17:42:28 +0000 (UTC), richard@ex-parrot.com (Richard
Smith) wrote:

> > > I can't see any licence in 5.16 for the compiler to copy
> > > either of its last two arguments, and the note at the end of
> > > 5.16/3, bullet 2, sub-bullet 1, "[Note: that is, no copy is
> > > made.]" seems to make this intent quite explicit even though
> > > that particular paragraph is not relevant here.
> >
> > It is not relevnat here and is meaningless here.  It states that
> > an lvalue to rvalue conversion does not create a copy.
>
> Not just this: 5.16/3 is more general than that.  Only
> bullet 1 makes requires either expression to be an lvalue.
> Bullet 2, sub-bullet 1 applies if both expressions are
> rvalues as is the case here.

Ok.  I was reading "E1 is changed to an rvalue of type T2"
as an lvalue to rvalue conversion.  It could also be an
rvalue of T1 to an rvalue of T2.  Class rvalues are objects.

> The reason that I said 5.16/3
> is not relevant here is that it starts "otherwise, if the
> second and third operand have different types" ... which
> they don't.  However, I would have thought that if the
> derived-to-base conversion didn't make a copy, then the
> identity conversion certainly shouldn't.  In particular
> 5.16/3 bullet 2, sub-bullet 1 appears to apply for this
> example.

>   struct B {};
>   struct D : B {};
>   struct X { B b; };

>   extern const bool x;

>   int main() {
>     x ? D() : X().b;
>   }

> It seems that in this case, the standard is quite explicit:
> no copy is made.

In evaluating that subexpression.  Then we get the old argument
about copies in general.  Is there anything in the standard
which prevents a copy in this case?

   B();

Or an lvalue to rvalue conversion in this case?

   B b;
   b;

> I got confused with another similar example that I
> had been testing:

>   struct B {};
>   struct D {};
>   extern const bool x;
>
>   int main() {
>     x ? D() : B();
>   }

That is an error and does not compile.  I guess you intended
D : B.

> In this case gcc does call the copy constructor specifically
> against the wording of 5.16/3.

Did it make a copy in evaluating the expression or afterwards
because it felt like it?

> > Note that binding to the B part of the D is not allowed.  That
> > would be allowed if D were derived from rather than contained B.

> Where did you get that from?  12.2/5 appears to contain
> the relevant text.

> | ... The temporary to which the reference is bound or the
> | temporary that is the complete object to a subobject of
> | which the temporary is bound persists for teh lifetime of
> | the reference except as specified below. ...

> "Subobject" is defined in 1.8/2 and explicitly includes both
> members and base classes.  As D().b returns the b member
> sub-object of the D temporary, this appears to require that
> the lifetime of D is extended.  (At least, unless you
> believe the copy constructor can be called.)

I see that D().b is an rvalue of type B.  There is nothing to
indicate that it is a subobject of a D.  OTOH, when the
expression is D() and D is derived from B, the rvalue is a D
and the reference is bound to the base subobject of it.  If
the expression were static_cast<B>(D()), the D would be as
unavailable as in the D().b case.

> > > Of the three compiler I
> > > tried, Comeau 4.3.0.1 appeared to make no attempt to extend
> > > its lifetime;

> > How did you reach that conclusion?

> From the fact that the following code

>   const B& b = x ? D().b : B();
>   printf("Foo\n");

> (with orchestrated constructors / destructors of B and D)
> destroyed all objects before printing Foo.

Ah.  That explains the author of the DR. :)

It seems that relaxing 8.5.3/5 to allow creating a copy of only
the base part would make life easier on implementers.  Any reason
for the current requirement to copy the entire object?

John

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Mon, 28 Jul 2003 14:41:01 +0000 (UTC)
Raw View
I've just been investigating a compiler bug in gcc involving
the following code, and have a couple of questions that I
hope members of this newsgroup might be able to help me
with.

  struct B {};
  struct D { B b; };

  extern const bool x;

  int main() {
    const B& b = x ? D().b : B();
  }


1)  According to the Standard, is D().b an rvalue or an
lvalue?  The subexpression D() is clearly an rvalue of type
D [5.2.3/2]. 5.2.5/4, bullet 2 appears to be the relevant text.
It says "if E1 is an lvalue, then E1.E2 is an lvalue".  It
does not, however, say that if E1 is an rvalue E1.E2 is an
rvalue.  Is this an oversight, or is its omission
intentional?  For that matter, does 3.10/6 mean that D().b
is an rvalue?  I think logically it has to be rvalue, but
I'm at a loss to find clear, normative text stating this.


2)  To establish whether compiler vendors think D().b is an
rvalue, I've tried the following code in Comeau 4.3.0.1, g++
3.2.3 and MSVC 7.1.

  struct B {};
  struct D { B b; int i; };
  int main() {
    D().b = B();   // (1)
    D().i = int(); // (2)
  }

In all three compilers, (1) compiled without diagnostic; in
Comeau and MSVC, (2) produced an error about D().i not being
an lvalue.  I presume that the compilers all compiled (1)
because it calls an implicitly declared memmber operator=,
and member functions may be called on rvalues [3.10/10].
However, I can't see any text overriding the requirement
that the LHS be a modifiable lvalue [5.17/1].  Does this
mean that all of these compilers are wrong, and neither (1)
nor (2) should compile?


3)  The second and third arguments to the ?: operator are
both of type B, the third argument is certainly an rvalue,
and the second probably is.  This means the result of the
conditional expression is an rvalue of type B [5.16/5-6].  I
can't see any licence in 5.16 for the compiler to copy
either of its last two arguments, and the note at the end of
5.16/3, bullet 2, sub-bullet 1, "[Note: that is, no copy is
made.]" seems to make this intent quite explicit even though
that particular paragraph is not relevant here.  This leads
me to believe that the following test case must not call the
copy constructor of B.

  struct B {};
  struct D { B b; int i; };
  extern const bool x;
  int main() {
    x ? D().b : B()
  }

Testing the code in the same three compilers reveals that
g++ 3.2.3 calls B's copy constructor when x == true, and
that the other compilers never call the copy constructor.
Is this a bug in g++?


4)  This result of the conditional is then bound to a
reference as per 8.5.3.  As the initialiser is an rvalue of
the same type as the reference being initialised, 8.5.3/5,
bullet 2, sub-bullet 1 applies.  This gives the compiler the
freedom to copy the result if necessary in an implementation
defined manner.  For example, in the following code, it
might choose to only make a copy if and only if x == true.

  B const& b = x ? D().b : B();


5)  The lifetime of the temporary is extended for the
lifetime of b [12.2/5].  Having looked at DR 86, which deals
specifically with binding conditional expressions to
temporaries, I note that "implementations should be required
to support these kinds of code".  Of the three compiler I
tried, Comeau 4.3.0.1 appeared to make no attempt to extend
its lifetime; g++ 3.2.3 tried, got confused, and double
destructed the D::b subobject; and MSVC 7.1 did it exactly
right.  Am I correct in thinking this is a bug in both g++
and the EDG front-end?


--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gdr@integrable-solutions.net (Gabriel Dos Reis)
Date: Mon, 28 Jul 2003 16:55:18 +0000 (UTC)
Raw View
richard@ex-parrot.com (Richard Smith) writes:

| 1)  According to the Standard, is D().b an rvalue or an
| lvalue?  The subexpression D() is clearly an rvalue of type
| D [5.2.3/2]. 5.2.5/4, bullet 2 appears to be the relevant text.
| It says "if E1 is an lvalue, then E1.E2 is an lvalue".  It
| does not, however, say that if E1 is an rvalue E1.E2 is an
| rvalue.  Is this an oversight, or is its omission
| intentional?

Funny you should raise the issue.
I raised this very issue recently on the Core Working Group reflector
and it appears that it is an oversight, and not a deliberate omission.
That sentence is supposed to end with "... otherwise it is an rvalue".

| For that matter, does 3.10/6 mean that D().b
| is an rvalue?  I think logically it has to be rvalue, but
| I'm at a loss to find clear, normative text stating this.

3.10/6 by itself is insufficient.  But the truth is that "D().b" is
intended to be an rvalue.

[...]

| In all three compilers, (1) compiled without diagnostic; in
| Comeau and MSVC, (2) produced an error about D().i not being
| an lvalue.  I presume that the compilers all compiled (1)
| because it calls an implicitly declared memmber operator=,
| and member functions may be called on rvalues [3.10/10].

Right.

| However, I can't see any text overriding the requirement
| that the LHS be a modifiable lvalue [5.17/1].

Because it does not have to.

| Does this
| mean that all of these compilers are wrong, and neither (1)
| nor (2) should compile?

No, it means that all three compilers are right on (1) and g++ is
wrong on (2).

--
Gabriel Dos Reis, gdr@integrable-solutions.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Mon, 28 Jul 2003 17:27:55 +0000 (UTC)
Raw View
Gabriel Dos Reis wrote:

> richard@ex-parrot.com (Richard Smith) writes:

> | 1)  According to the Standard, is D().b an rvalue or an
> | lvalue?  The subexpression D() is clearly an rvalue of type
> | D [5.2.3/2]. 5.2.5/4, bullet 2 appears to be the relevant text.
> | It says "if E1 is an lvalue, then E1.E2 is an lvalue".  It
> | does not, however, say that if E1 is an rvalue E1.E2 is an
> | rvalue.  Is this an oversight, or is its omission
> | intentional?
>
> Funny you should raise the issue.
> I raised this very issue recently on the Core Working Group reflector
> and it appears that it is an oversight, and not a deliberate omission.
> That sentence is supposed to end with "... otherwise it is
> an rvalue".

It's nice to know someone else had noticed it too!   I'd
guessed that the intention was that it should be an rvalue.

> | However, I can't see any text overriding the requirement
> | that the LHS be a modifiable lvalue [5.17/1].
>
> Because it does not have to.

I'm not sure I fully see this.  5.17/1 says

| There are several assignment operators, all of which group
| right-to-left.  All require a modifiable lvalue as their
| left operand, and the type of an assignment expression is
| that of its left operand.

It doesn't say that this applies only to built-in operators,
and the fact that some subsequent paragraphs (e.g. 5.17/4)
start with text like "if the left operand is of class type"
suggests to me that 5.17/1 applies to both built-in and
user-defined assignment operators.

Clearly the comment about assignment operators grouping from
right-to-left applies to all assignment operators, as does
the subsequent fragment of the grammar.  Where does it state
that the modifiable lvalue requirement only applies to
built-in assignment operators?

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: Mon, 28 Jul 2003 20:57:30 +0000 (UTC)
Raw View
On Mon, 28 Jul 2003 14:41:01 +0000 (UTC), richard@ex-parrot.com (Richard
Smith) wrote:

> I've just been investigating a compiler bug in gcc involving
> the following code, and have a couple of questions that I
> hope members of this newsgroup might be able to help me
> with.

>   struct B {};
>   struct D { B b; };
>
>   extern const bool x;
>
>   int main() {
>     const B& b = x ? D().b : B();
>   }

The ?: only adds confusion.  Gcc performs the same with

   const B& b = D().b;

> 1)  According to the Standard, is D().b an rvalue or an
> lvalue?  The subexpression D() is clearly an rvalue of type
> D [5.2.3/2]. 5.2.5/4, bullet 2 appears to be the relevant text.
> It says "if E1 is an lvalue, then E1.E2 is an lvalue".  It
> does not, however, say that if E1 is an rvalue E1.E2 is an
> rvalue.

It looks like an implied otherwise to me.

> Is this an oversight, or is its omission
> intentional?  For that matter, does 3.10/6 mean that D().b
> is an rvalue?  I think logically it has to be rvalue, but
> I'm at a loss to find clear, normative text stating this.

Agreed.

> 2)  To establish whether compiler vendors think D().b is an
> rvalue, I've tried the following code in Comeau 4.3.0.1, g++
> 3.2.3 and MSVC 7.1.

>   struct B {};
>   struct D { B b; int i; };
>   int main() {
>     D().b = B();   // (1)
>     D().i = int(); // (2)
>   }

> In all three compilers, (1) compiled without diagnostic; in
> Comeau and MSVC, (2) produced an error about D().i not being
> an lvalue.  I presume that the compilers all compiled (1)
> because it calls an implicitly declared memmber operator=,
> and member functions may be called on rvalues [3.10/10].

Yes, and operator= returns a reference which is always an
lvalue.

> However, I can't see any text overriding the requirement
> that the LHS be a modifiable lvalue [5.17/1].

This is the built-in operator not a function which happens to
have the same name.  Operator functions have different rules.
See 5/2.

> Does this
> mean that all of these compilers are wrong, and neither (1)
> nor (2) should compile?

No.  (1) should and (2) should not.  Change (2) to

   (D() = D()).i = int();

and it will also compile.  The infamous rvalue to lvalue
conversion.

> 3)  The second and third arguments to the ?: operator are
> both of type B, the third argument is certainly an rvalue,
> and the second probably is.  This means the result of the
> conditional expression is an rvalue of type B [5.16/5-6].  I
> can't see any licence in 5.16 for the compiler to copy
> either of its last two arguments, and the note at the end of
> 5.16/3, bullet 2, sub-bullet 1, "[Note: that is, no copy is
> made.]" seems to make this intent quite explicit even though
> that particular paragraph is not relevant here.

It is not relevnat here and is meaningless here.  It states that
an lvalue to rvalue conversion does not create a copy.  There are
no lvalues present.

> This leads
> me to believe that the following test case must not call the
> copy constructor of B.

>   struct B {};
>   struct D { B b; int i; };
>   extern const bool x;
>   int main() {
>     x ? D().b : B()
>   }

The standard allows making or omiting copy ctor calls in about
all cases involving rvalues.

> Testing the code in the same three compilers reveals that
> g++ 3.2.3 calls B's copy constructor when x == true, and
> that the other compilers never call the copy constructor.
> Is this a bug in g++?

How did you conclude that gcc called the copy ctor?  My tests
indicate that it created a copy without calling the copy ctor.
That is an error.  It then destroyed both copies.

> 4)  This result of the conditional is then bound to a
> reference as per 8.5.3.  As the initialiser is an rvalue of
> the same type as the reference being initialised, 8.5.3/5,
> bullet 2, sub-bullet 1 applies.  This gives the compiler the
> freedom to copy the result if necessary in an implementation
> defined manner.

Where did you find that?  It gives the compiler the freedom to
make a copy but not to bypass the copy ctor in doing it.  Gcc
makes a copy without calling the copy ctor.

> For example, in the following code, it
> might choose to only make a copy if and only if x == true.

>   B const& b = x ? D().b : B();

Right.  Gcc creates space for a temporary for binding with b.
When x is false, it creates the after colon B in that space
avoiding a copy.  When x is true, it creates the D and copies
the B part to that space without using the copy ctor.  That is
the error.

Note that binding to the B part of the D is not allowed.  That
would be allowed if D were derived from rather than contained B.
In the false case, a B dtor would be called at the end of the
function.  In the true case, a D dtor would be called at the
end of the function.  Gcc seems to have made a wise decision,
but used an invalid bitwise copy in place of the required copy
ctor.

> 5)  The lifetime of the temporary is extended for the
> lifetime of b [12.2/5].  Having looked at DR 86, which deals
> specifically with binding conditional expressions to
> temporaries, I note that "implementations should be required
> to support these kinds of code".

That's an interesting DR.  Only one of the subexpressions is
evaluated in the ?: expression.  How can the lifetime of a
temporary which was never created be extended?  It also seems
that the requirement to copy the entire derived object for
binding to the base object creates a problem.  If the example
here is changed to derive D from B, gcc makes a copy of only
the B part of the D and uses the copy ctor to do it.  It
is not allowed to make a copy of only the B part, but it does
make the copy in the correct way.  :)

> Of the three compiler I
> tried, Comeau 4.3.0.1 appeared to make no attempt to extend
> its lifetime;

How did you reach that conclusion?

> g++ 3.2.3 tried, got confused, and double
> destructed the D::b subobject;

No, it created a copy WITHOUT using the copy ctor and extended its
lifetime to the lifetime of the reference.  If you print the this
pointer in the destructor and add some output to main, you will see
that there is a copy and that the two are destroyed at different
times.  It is an error, but not what you thought.

John

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gdr@integrable-solutions.net (Gabriel Dos Reis)
Date: Tue, 29 Jul 2003 01:34:43 +0000 (UTC)
Raw View
richard@ex-parrot.com (Richard Smith) writes:

[...]

| It's nice to know someone else had noticed it too!   I'd
| guessed that the intention was that it should be an rvalue.

Yes.

[...]

| It doesn't say that this applies only to built-in operators,
| and the fact that some subsequent paragraphs (e.g. 5.17/4)
| start with text like "if the left operand is of class type"
| suggests to me that 5.17/1 applies to both built-in and
| user-defined assignment operators.

You have to look at the opening paragraphs of clause 5, specifically
5/2 -- which admitelly is a note:

  Operators can be overloaded, that is, given meaning when applied to
  expressions of class type (clause 9) or enumeration type (7.2). Uses
  of overloaded operators are transformed into function calls as
  described in 13.5. Overloaded operators obey the rules for syntax
  specified in clause 5, but the requirements of operand type, lvalue,
  and evaluation order are replaced by the rules for function
  call. Relations between operators, such as ++a meaning a+=1, are not
  guaranteed for overloaded operators (13.5), and are not guaranteed
  for operands of type bool.

| Clearly the comment about assignment operators grouping from
| right-to-left applies to all assignment operators, as does
| the subsequent fragment of the grammar.  Where does it state
| that the modifiable lvalue requirement only applies to
| built-in assignment operators?

normative text, 5/3:

  Clause 5 defines the effects of operators when applied to types for
  which they have not been overloaded. [...]

--
Gabriel Dos Reis, gdr@integrable-solutions.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: johnchx2@yahoo.com (johnchx)
Date: Tue, 29 Jul 2003 01:35:07 +0000 (UTC)
Raw View
richard@ex-parrot.com (Richard Smith) wrote
>
> > | However, I can't see any text overriding the requirement
> > | that the LHS be a modifiable lvalue [5.17/1].
> >
> > Because it does not have to.
>
> I'm not sure I fully see this.  5.17/1 says
>
> | There are several assignment operators, all of which group
> | right-to-left.  All require a modifiable lvalue as their
> | left operand, and the type of an assignment expression is
> | that of its left operand.
>
> It doesn't say that this applies only to built-in operators,
> and the fact that some subsequent paragraphs (e.g. 5.17/4)
> start with text like "if the left operand is of class type"
> suggests to me that 5.17/1 applies to both built-in and
> user-defined assignment operators.

5/2 says:
  Overloaded operators obey the rules for syntax specified in
  clause 5, but the requirements of operand type, lvalue, and
  evaluation order are replaced by the rules for function call.

13.5/7 says:
  Some predefined operators, such as +=, require an operand to
  be an lvalue when applied to basic types; this is not required
  for operator functions.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Tue, 29 Jul 2003 14:50:09 +0000 (UTC)
Raw View
johnchx wrote:

> 5/2 says:
>   Overloaded operators obey the rules for syntax specified in
>   clause 5, but the requirements of operand type, lvalue, and
>   evaluation order are replaced by the rules for function call.
>
> 13.5/7 says:
>   Some predefined operators, such as +=, require an operand to
>   be an lvalue when applied to basic types; this is not required
>   for operator functions.

Thanks.  I stand corrected ;-).

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Tue, 29 Jul 2003 17:42:12 +0000 (UTC)
Raw View
Gabriel Dos Reis wrote:

> richard@ex-parrot.com (Richard Smith) writes:
>
> | It doesn't say that this applies only to built-in operators,
> | and the fact that some subsequent paragraphs (e.g. 5.17/4)
> | start with text like "if the left operand is of class type"
> | suggests to me that 5.17/1 applies to both built-in and
> | user-defined assignment operators.
>
> You have to look at the opening paragraphs of clause 5, specifically
> 5/2 [...]

OK, I guessed there must be something like that that I had
missed.  Thanks for clearing it up.

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Tue, 29 Jul 2003 17:42:28 +0000 (UTC)
Raw View
Thanks for taking the time to write a detailed reply.

John Potter wrote:
> Richard Smith wrote:
>
> >   int main() {
> >     const B& b = x ? D().b : B();
> >   }
>
> The ?: only adds confusion.  Gcc performs the same with
>
>    const B& b = D().b;

I discovered this, too.  The reason I left the original code
in was because I was unsure about some of the semantics of
the ternary operator when acting on rvalues.

> > It says "if E1 is an lvalue, then E1.E2 is an lvalue".  It
> > does not, however, say that if E1 is an rvalue E1.E2 is an
> > rvalue.
>
> It looks like an implied otherwise to me.

Agreed.

> Yes, and operator= returns a reference which is always an
> lvalue.

Agreed, although I'm not sure that it is relevant to the
current example.

> > However, I can't see any text overriding the requirement
> > that the LHS be a modifiable lvalue [5.17/1].
>
> This is the built-in operator not a function which happens to
> have the same name.  Operator functions have different rules.
> See 5/2.

I now agree with this.  Several others have also pointed out
this paragraph.


> > I can't see any licence in 5.16 for the compiler to copy
> > either of its last two arguments, and the note at the end of
> > 5.16/3, bullet 2, sub-bullet 1, "[Note: that is, no copy is
> > made.]" seems to make this intent quite explicit even though
> > that particular paragraph is not relevant here.
>
> It is not relevnat here and is meaningless here.  It states that
> an lvalue to rvalue conversion does not create a copy.

Not just this: 5.16/3 is more general than that.  Only
bullet 1 makes requires either expression to be an lvalue.
Bullet 2, sub-bullet 1 applies if both expressions are
rvalues as is the case here.  The reason that I said 5.16/3
is not relevant here is that it starts "otherwise, if the
second and third operand have different types" ... which
they don't.  However, I would have thought that if the
derived-to-base conversion didn't make a copy, then the
identity conversion certainly shouldn't.  In particular
5.16/3 bullet 2, sub-bullet 1 appears to apply for this
example.

  struct B {};
  struct D : B {};
  struct X { B b; };

  extern const bool x;

  int main() {
    x ? D() : X().b;
  }

It seems that in this case, the standard is quite explicit:
no copy is made.

> The standard allows making or omiting copy ctor calls in about
> all cases involving rvalues.

In general, yes; but I can't find text allowing it in this
particular case.  Nor can I find a catch-all paragraph that
might apply to this case.

> > Testing the code in the same three compilers reveals that
> > g++ 3.2.3 calls B's copy constructor when x == true, and
> > that the other compilers never call the copy constructor.
> > Is this a bug in g++?
>
> How did you conclude that gcc called the copy ctor?  My tests
> indicate that it created a copy without calling the copy ctor.
> That is an error.  It then destroyed both copies.

Sorry.  I got confused with another similar example that I
had been testing:

  struct B {};
  struct D {};
  extern const bool x;

  int main() {
    x ? D() : B();
  }

In this case gcc does call the copy constructor specifically
against the wording of 5.16/3.  You're quite right, the copy
constructor is not called in the example I originally
gave.

> > 4)  This result of the conditional is then bound to a
> > reference as per 8.5.3.  As the initialiser is an rvalue of
> > the same type as the reference being initialised, 8.5.3/5,
> > bullet 2, sub-bullet 1 applies.  This gives the compiler the
> > freedom to copy the result if necessary in an implementation
> > defined manner.
>
> Where did you find that?  It gives the compiler the freedom to
> make a copy but not to bypass the copy ctor in doing it.

I didn't mean that it was allowed to make a copy without
calling the copy constructor -- clearly it's not allowed to
do that.  What I meant was that the compiler could choose in
an implementation defined manner when to make a copy (via
the copy constructor) and when to use the orignal object.

> Gcc makes a copy without calling the copy ctor.

Indeed, and I agree it's not allowed to do this.

> > For example, in the following code, it
> > might choose to only make a copy if and only if x == true.
>
> >   B const& b = x ? D().b : B();
>
> Right.  Gcc creates space for a temporary for binding with b.
> When x is false, it creates the after colon B in that space
> avoiding a copy.  When x is true, it creates the D and copies
> the B part to that space without using the copy ctor.  That is
> the error.

Yes, but it is only allowed to call the copy constructor
because the result is being bound to a reference.  In the
conditional expression on it's own, it is not allowed to
make a copy, but it does.  (Admitedly without using the copy
constructor, though.)

> Note that binding to the B part of the D is not allowed.  That
> would be allowed if D were derived from rather than contained B.

Where did you get that from?  12.2/5 appears to contain
the relevant text.

| ... The temporary to which the reference is bound or the
| temporary that is the complete object to a subobject of
| which the temporary is bound persists for teh lifetime of
| the reference except as specified below. ...

"Subobject" is defined in 1.8/2 and explicitly includes both
members and base classes.  As D().b returns the b member
sub-object of the D temporary, this appears to require that
the lifetime of D is extended.  (At least, unless you
believe the copy constructor can be called.)

> That's an interesting DR.  Only one of the subexpressions is
> evaluated in the ?: expression.  How can the lifetime of a
> temporary which was never created be extended?

Slightly sloppy wording, I expect

  "Do the temporaries created by the SFileName conversions
  survive the end of the full expression?"

should have been

  "Does the temporary created by the SFileName conversion
  survive the end of the full expression?"

>  It also seems
> that the requirement to copy the entire derived object for
> binding to the base object creates a problem.  If the example
> here is changed to derive D from B, gcc makes a copy of only
> the B part of the D and uses the copy ctor to do it.  It
> is not allowed to make a copy of only the B part, but it does
> make the copy in the correct way.  :)

Indeed.  I don't think it's allowed to make *any* copies,
whether of B or D.

> > Of the three compiler I
> > tried, Comeau 4.3.0.1 appeared to make no attempt to extend
> > its lifetime;
>
> How did you reach that conclusion?

>From the fact that the following code

  const B& b = x ? D().b : B();
  printf("Foo\n");

(with orchestrated constructors / destructors of B and D)
destroyed all objects before printing Foo.

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]