[Voted into WP at October 2005 meeting.]
Proposed resolution (October 2004): Change 7.6.16 [expr.cond] bullet 3.2
sub-bullet 1 as follows: Change 7.6.16 [expr.cond] bullet 6.1 as
follows: Change 7.3.2 [conv.lval] paragraph 2 as follows: [Note: this wording partially resolves issue 86. See also issue 462.]
if E1 and E2 have class type, and the
underlying class types are the same or one is a base class of the
other: E1 can be converted to match E2 if the
class of T2 is the same type as, or a base class of, the
class of T1 , and the cv-qualification of T2 is
the same cv-qualification as, or a greater cv-qualification than,
the cv-qualification of T1 . If the conversion is
applied, E1 is changed to an rvalue of type T2
that still refers to the original source class object (or the
appropriate subobject thereof). [Note: that is, no copy is
made. —end note] by copy-initializing a
temporary of type T2 from E1 and using that
temporary as the converted operand.
The second and third operands have the same type; the result is
of that type. If the operands have class type, the result is an
rvalue temporary of the result type, which is copy-initialized
from either the second operand or the third operand depending on
the value of the first operand.
The value contained in the object indicated by the lvalue is
the rvalue result. When an lvalue-to-rvalue conversion occurs
within the operand of sizeof (7.6.2.5 [expr.sizeof]) the value contained in the referenced object is
not accessed, since that operator does not evaluate its operand.
Otherwise, if the lvalue has a class type, the conversion
copy-initializes a temporary of type T from the lvalue
and the result of the conversion is an rvalue for the temporary.
Otherwise, the value contained in the object indicated by the
lvalue is the rvalue result.
Notes from the March 2004 meeting: We decided all "?" operators that return a class rvalue should
copy the second or third operand to a temporary. See
issue 86.
The problem occurs when the value of the operator is determined to
be an rvalue, the selected argument is an lvalue, the type is a class
type and a non-const member is invoked on the modifiable rvalue result. The types of the second and third operands are the same and
one is an rvalue. Nothing changes until p6 where an lvalue to
rvalue conversion is performed on the third operand.
6.8.7 [class.temporary] states that an lvalue to rvalue
conversion produces a temporary and there is nothing to remove
it. It seems clear that the assertion must pass, yet most
implementations fail. There seems to be a defect in p3 b2 b1. First, the conditions to get
here and pass the test. If both E1 and E2 are lvalues, passing the conditions here also passes
the conditions for p3 b1. Thus, at least one is an rvalue. The case of
two rvalues is not interesting and the action is covered by the case
when E1 is an rvalue. Having changed the rvalue to base type, we are back to the above case
where an lvalue to rvalue conversion is required on the third operand
at p6. Again, most implementations fail. The remaining case, E1 an lvalue and E2 an rvalue, is the defect. The above quote states that an lvalue of type T1 is changed to an rvalue
of type T2 without creating a temporary. This is in contradiction to
everything else in the standard about lvalue to rvalue conversions.
Most implementations pass in spite of the defect. The usual accessible and unambiguous is missing from the base class. There seems to be two possible solutions. Following other temporary
creations would produce a temporary rvalue of type T1 and change it
to an rvalue of type T2. Keeping the no copy aspect of this bullet
intact would change the lvalue of type T1 to an lvalue of type T2.
In this case the lvalue to rvalue conversion would happen in p6 as
usual. Suggested wording for p3 b2 b1 The base part: The same type temporary version: The never copy version: The test case was posted to clc++m and results for implementations
were reported. I see no defect with regards to lvalue to rvalue conversions; however,
there seems to be disagreement about what it means by implementers.
It may not be surprising because 5.16 and passing a POD struct to an
ellipsis are the only places where an lvalue to rvalue conversion
applies to a class type. Most lvalue to rvalue conversions are on
basic types as operands of builtin operators.
struct B {
int v;
B (int v) : v(v) { }
void inc () { ++ v; }
};
struct D : B {
D (int v) : B(v) { }
};
B b1(42);
(0 ? B(13) : b1).inc();
assert(b1.v == 42);
If E1 and E2 have class type, and the underlying class types are the
same or one is a base class of the other: E1 can be converted to
match E2 if the class of T2 is the same type as, or a base class of,
the class of T1, and the cv-qualification of T2 is the same
cv-qualification as, or a greater cv-qualification than, the
cv-qualification of T1.
(0 ? D(13) : b1).inc();
assert(b1.v == 42);
E1 is changed to an rvalue of type T2 that still refers to the
original source class object (or the appropriate subobject thereof).
[Note: that is, no copy is made. ]
D d1(42);
(0 ? B(13) : d1).inc();
assert(d1.v == 42);
If E1 and E2 have class type, and the underlying class types are the
same or one is a base class of the other: E1 can be converted to match
E2 if the class of T2 is the same type as, or an accessible and
unambiguous base class of, the class of T1, and the cv-qualification
of T2 is the same cv-qualification as, or a greater cv-qualification
than, the cv-qualification of T1. If the conversion is applied:
If E1 is an lvalue, an lvalue to rvalue conversion is applied. The
resulting or original rvalue is changed to an rvalue of type T2 that
refers to the same class object (or the appropriate subobject
thereof). [Note: that is, no copy is made in changing the type of
the rvalue. ]
The lvalue(rvalue) E1 is changed to an lvalue(rvalue) of type T2
that refers to the original class object (or the appropriate
subobject thereof). [Note: that is, no copy is made. ]
#include <cassert>
struct B {
int v;
B (int v) : v(v) { }
void inc () { ++ v; }
};
struct D : B {
D (int v) : B(v) { }
};
int main () {
B b1(42);
D d1(42);
(0 ? B(13) : b1).inc();
assert(b1.v == 42);
(0 ? D(13) : b1).inc();
assert(b1.v == 42);
(0 ? B(13) : d1).inc();
assert(d1.v == 42);
}
// CbuilderX(EDG301) FFF Rob Williscroft
// ICC-8.0 FFF Alexander Stippler
// COMO-4.301 FFF Alexander Stippler
// BCC-5.4 FFP Rob Williscroft
// BCC32-5.5 FFP John Potter
// BCC32-5.65 FFP Rob Williscroft
// VC-6.0 FFP Stephen Howe
// VC-7.0 FFP Ben Hutchings
// VC-7.1 FFP Stephen Howe
// OpenWatcom-1.1 FFP Stephen Howe
// Sun C++-6.2 PFF Ron Natalie
// GCC-3.2 PFP John Potter
// GCC-3.3 PFP Alexander Stippler
// GCC-2.95 PPP Ben Hutchings
// GCC-3.4 PPP Florian Weimer