C++ PATCH: Bug in ?: operator

Mark Mitchell mark@codesourcery.com
Wed Sep 1 08:25:00 GMT 1999


    So, unless explicitly prohibited by [basic.def.odr]/4 or more specific
    clause, an incomplete type is ok. Thus
	    struct S;
	    void fn (S *sp) {
		    (void)*sp;
	    }
    is legal. I've explicitly put in a cast to void, but it is not
    necessary, as [stmt.expr]/1 contains the same language as
    [expr.static.cast]/4. (theorem 1)

Agreed; anything can be cast to `void', and an incomplete type can be
dereferenced as you do there.

    thus, converting theorem 1 from a pointer into a reference, gives
	    struct S;
	    void fn (S &sr) {
		    (void)sr;
	    }
    which is similarly legal. (theorem 2)

Yes; here just because anything can be cast to void.

    In both these cases, we obviously cannot fetch the data, because we have
    no idea about the object layout. AFAICT, it is the non-occurrance of the
    lvalue-to-rvalue conversion which is allowing the incompleteness.

I agree with the first sentence; not with the second.

    [conv.lval]4.1/1
	    An lvalue of non-function non-array type T can be converted
	    to an rvalue. If T is an incomplete type, a program that
	    necessitates this conversion is ill-formed.
    So explicitly casting to the type, S, before casting to void, as in
	    (void)(S)*sp;
	    (void)(S)sr;
    does cause lvalue-to-rvalue decay (because [expr.cast]5.4/1 says so),
    and
    hence ill-formed when S is incomplete. (theorem 3)

Yes these are illegal.  That's because `S s (*sp);' and `S s (sr)'
would be illegal.  In [expr.static.cast]:

  An expression e can be explicitly  converted  to  a  type  T  using  a
  static_cast of the form static_cast<T>(e) if the declaration T
  t(e);" is well-formed, for some invented temporary variable  t     

Those invented declarations would be illegal because `S' is
incomplete, not because of the lvalue-to-rvalue conversion.

  Now, what about
	  struct S;
	  void fn (volatile S *sp, volatile S &sr) {
		  (void)*sp;
		  (void)sr;
	  }
  If theorem 1 & 2 are true, I can't see anything in the standard which
  makes this ill-formed. (theorem 4). 

Agreed; anything can be cast to void, and incomplete types can be
dereferenced.  The volatility doesn't enter into it.

  This of course, brings us to the contentious case where S is a complete
  type and we have a volatile qualified version,
	  struct S {int m;};
	  void fn (volatile S *vsp, volatile S &vsr) {
		  (void)*vsp;
		  (void)vsr;
	  }
  Again, I can't see that anything changes WRT [expr.static.cast]/4 which
  would turn these into reads of a volatile object. (theorem 5).

Or anything that prevents these from being so.  We should indeed treat
these as reads from memory, for compatibility with C, and because
there's no reason not to.

  Notice the code in cplus_expand_expr_stmt (decl.c) which strips the
  implicit INDIRECT_REF off a reference type expression supports this
  interpretation for a reference type. C++ treats
	  T *const p = X; ... *p
  the same as
	  T &r = X; ... r
  does it not?

  I think it would be daft for theorem 4 to be true and theorem 5 not
  true -- S's definition could be miles away from the contentious
  expression.  It would be surprising for theorem 1 to be true and
  theorem 2 not to be -- references are like pointers with an implicit
  `*'. Theorem 4 is linked to 1 & 2, as the relevant information is the
  incompleteness of S -- there doesn't seem to be anything saying that an
  cv-unqualified incomplete type behaves differently to a volatile
  qualified incomplete type.

I think we're basically agreeing.

  Yes, but I don't see how this is directly relevant. With a volatile
  access we can't invoke this as-if rule, because, of course, something
  external might detect a difference. What the above is about is whether
  an access occurs at all -- via a consistency argument so the programmer
  can understand the meaning of what was written without global knowlege
  of the program.

It would be nice if local information were sufficient in C++, but it's
just not.  You can't even tell whether `(S) s' is illegal, some flavor
of cast that generates code, some flavor of cast that doesn't,
etc. without global information.

Access to volatile things should happen whenever the programmer looks
at the value of volatile storage.  Ssaying `*vp' constitutes that for
`vp' a pointer to a volatile type.  

We should imagine writing the undergraduate C++ interpreter, that does
a simple recursive evaluation of expressions.  When it sees `*vp' (in
an rvalue context) it loads the memory pointed to.  If you say `*&*vp'
is loads it twice.  Etc.

  * If the lvalue is accessed, one cannot allow incomplete lvalues in
  cast-to-void because then the observable behaviour of `(void)*vsp'
  depends on the completeness of S (I consider this a bad thing). Also it
  is impossible to ignore the result of `volatile T &fn()' (I think that
  a stupid function to define).

Yes, it is odd that the observable behavior depends on the
completeness of S.  But, I think that it does.

  No doubt you'll know the relevant section of the standard to disprove
  all this :-)

:-)  Not really.  But, I think the burden is on you. :-)  Here's what
I think:

  o Saying `*vp;' for a volatile pointer `vp' is a well-known idiom.
    It should read the memory.  Obviously it cannot do so if `vp' is
    a pointer to an incomplete type.  Oh, well.  Lvalues of incomplete
    type can only be used in very limited ways; that's made quite
    clear in the standard.  (Basically, you can take their address
    again.)

  o Saying `(void) x' for (almost) any `x' is legal, and should have
    absolutely no effect on what goes on in `x'.  It just means
    "I didn't need the result of that computation."

    One reason casting to `void' avoids lots of conversions is so that
    you can write `(void) (.... T ...)' in a template, and not worry
    about conversions firing that might not be legal for some value of
    T.

These two issues are orthogonal.

I think whatever bits of machinery to do these machinations with casts
to void and with volatile-handling are currently in G++ should be
removed.

--
Mark Mitchell                   mark@codesourcery.com
CodeSourcery, LLC               http://www.codesourcery.com


More information about the Gcc-patches mailing list