This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
Re: C++ PATCH: Bug in ?: operator
- To: nathan at cs dot bris dot ac dot uk, nathan at acm dot org
- Subject: Re: C++ PATCH: Bug in ?: operator
- From: Mark Mitchell <mark at codesourcery dot com>
- Date: Wed, 01 Sep 1999 08:30:35 -0700
- Cc: jason at cygnus dot com, gcc-patches at gcc dot gnu dot org, Gabriel dot Dos-Reis at cmla dot ens-cachan dot fr
- Organization: CodeSourcery, LLC
- References: <37C6C54C.EE7FBE9@acm.org><19990827112730C.mitchell@codesourcery.com><37CCEA25.D35BF468@acm.org>
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