Topic: Defect Report: Can an rvalue refer to an lvalue's object?


Author: John Potter <jpotter@falcon.lhup.edu>
Date: Tue, 06 Jan 2004 14:54:41 GMT
Raw View
 [Moderator's note: this defect report has been
 forwarded to the C++ committee. -moderator.]

5.16 Conditional Operator [expr.cond]

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.

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

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

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

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.

    D d1(42);
    (0 ? B(13) : d1).inc();
    assert(d1.v == 42);

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:

  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:

The same type temporary version:

  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 never copy version:

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

The test case was posted to clc++m and results for implementations
were reported.

#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

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.

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                       ]