Topic: implicit pointer of pointer argument promotion legal C++?
Author: pat@frumious.uucp (Patrick Smith)
Date: 4 Nov 92 02:56:39 GMT Raw View
sdm@cs.brown.edu (Scott Meyers) writes:
|| >Hence, given that this is legal,
|| >
|| > T *p;
|| > void *q = p;
|| >
|| >is there a good reason why this isn't?
|| >
|| > void f(T*);
|| > void (*pf)(void*) = f;
|
|Clearly (I hope), if pf wants to do anything with its
|argument, it has to know what the real type is, i.e., it's going to have to
|do an explicit cast from void* to whatever the type really is. Obviously
|this gives rise to the spector of type errors, but so does any use of void*
|pointers. So my question is really whether there are any possibilities of
|type errors other than those inherent in the use of void* pointers.
Well, nobody seems to have pointed out any other semantic errors yet.
That's with the understanding that converting
void (*) (T*) to void (*) (void*)
is equivalent to converting void* to T*, and both of which can
cause type errors and should require a cast.
Here's another way of looking at the question.
Ask yourself why one would want to declare pf this way:
void (*pf)(void*);
If pf is only going to be called with T*'s as arguments, then it
would probably be better to declare pf as taking a T* argument
instead of a void*. So when we see this declaration, we should
assume that pf may be called with more than one type of pointer
as an argument. (I'm sure there are exceptions to this, but it
should be true as a general rule.) In fact, the function to which
pf points may not even care what type of object its argument points to.
What if we write, as above,
void f(T*);
void (*pf)(void*) = f;
What should this indicate?
Since the parameter to pf is a void*, there are probably several
different pointer types which could be used as arguments to pf.
On the other hand, for so long as pf points to f, we'd better not
call pf with any pointer that's not a T*. The compiler can't protect
us here, though; it has to accept any pointer as a parameter to pf.
So it's up to the program logic to guarantee that the argument
to pf will always be acceptable to the function pointed to by pf.
Situations such as this can easily cause subtle bugs. Especially when
someone other than the original developer has to maintain the
program, perhaps years later. The explicit cast is roughly
equivalent to a sign saying "Warning - there be dragons here!".
So I think it's a good idea that the cast be required.
(Now that I've said all that... I'm quite prepared to believe that
Scott has encountered a situation analogous to this, but where
pf will only be called with a T* for an argument and there are
good reasons for declaring pf to take a void* argument anyway.
Or that pf might take several different pointer types as arguments
and he's confident he will always get it right. But I think most
of the rest of us would benefit from having the compiler force us
to do the equivalent of saying, "Yes, I know it looks strange, but
that's really what I meant". I know I would.)
--
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca
Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Wed, 4 Nov 1992 07:05:44 GMT Raw View
In article <1cpssnINN3bj@ector.cs.purdue.edu> gb@cs.purdue.edu (Gerald Baumgartner) writes:
>In article <1992Oct29.195546.1@vax1.bham.ac.uk> mccauleyba@vax1.bham.ac.uk writes:
>
>I think the key is to view `const' as a `location specifier' instead of
>a `type specifier'.
No, IMHO it is an 'access specifier'. Const in C++ constrains
access via some mechanism, e.g. a pointer or reference, it is NOT
a property of the storage. (That would be nice to have though)
For example:
int x;
int &r=x;
const int &c=x;
It is the access to x via c that is restricted to 'const',
not the storage location (variable). In fact I think that x is
just a reference like r that happens to also allocate storage.
>
>But does it make sense to view `const' as a type specifier? I think
>not. A type is, semantically, a set of values. All of those values
>are constant.
>Also, it just doesn't make sense to talk about a
>function returning a `const T.' If `const' were a type specifier,
>then a function returning `const T' would need to have some kind of
>meaning.
Same applies to parameters. f(const int) and f(int)
are senselessly distinct functions.
>
>It's only variables (or locations in general) for which a `const'
>qualification is useful. This way we are asserting, that the value of
>this location is not allowed to change.
You could have this facility, but 'const' is an access specifier
not a property of the storage. If one ALSO allowed the 'rom' storage
attribute you could disallow taking a non-const pointer or reference
to it.
rom int x;
int &y=x; /// compiler error
>
>I propose to call `const' and `volatile' `location specifiers' which
>qualify the next variable (or parameter) name or the next `*' instead
>of qualifying the next type identifier. For example,
>
> const T x;
>
>declares the memory location of `x' to be a readonly location, and
>
> const T * p;
>
>declares `p' to be a pointer to a readonly location.
This is not quite right. Const might also mean 'logical const',
it does not ensure constness cant be cast away harmlessly to
maintain a cache. So there is yet another meaning to const.
Perhaps 'rom' is a better keyword here.
>
>Now both of the examples above are safe.
No, because one can cast away const. But we could make it
that 'rom' cannot be cast away. Alternatively, another keyword
'immutable' could mean logical const. The problem is that for
a class 'const' applies at three levels, public protected and private.
Something can be publicaly constant but still modifiable privately.
This is not the same as the storage location being rommable,
because even an object in rom may be publically mutable
(by changing a related store via a pointer).
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Wed, 4 Nov 1992 10:32:39 GMT Raw View
In article <1992Nov2.193135.6803@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
>In article <1d1oirINN3vu@agate.berkeley.edu> jbuck@forney.berkeley.edu (Joe Buck) writes:
>| In article <1992Nov1.201547.4661@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
>| >Hence, given that this is legal,
>| > ..
>| >is there a good reason why this isn't?
>| >
>| > void f(T*);
>| > void (*pf)(void*) = f;
>|
>| Certainly. Consider the line that would be legal next:
>|
>| T2* p; // class T2 has no relation to class T
>| pf(p);
>|
>| Now we have passed a pointer to T2 to a function that thinks it is getting
>| a pointer to T, and we have broken the type system.
>
>Boy, either I'm really dense or what we have here is a failure to
>communicate. Clearly (I hope), if pf wants to do anything with its
^^^^^^^^^^^^^^^^^
>argument, it has to know what the real type is, i.e., it's going to have to
>do an explicit cast from void* to whatever the type really is. Obviously
> ...
You _must_ have been day-dreaming a bit.
'pf' is only a pointer, not a function, so it cannot _do_ anything!
The argument is going to the function (i.e., 'f'), not to 'pf'.
'pf(p)' is only shorthand (and obviously often confusing such)
for '(pf*)(p)'.
----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------
Author: qbarnes@urbana.mcd.mot.com (Quentin Barnes)
Date: Thu, 22 Oct 1992 21:28:02 GMT Raw View
In <23922@alice.att.com> ark@alice.att.com (Andrew Koenig) writes:
>In article <1992Oct15.222018.16565@urbana.mcd.mot.com> qbarnes@urbana.mcd.mot.com (Quentin Barnes) writes:
>> Sorry, you missed the point. I was refering to _argument_ promotion.
>> Your example does not address this issue.
>> [...]
>> f() would like to make the same promise that it will not modify the
>> object, but because of the ANSI-C argument promotion rules, it cannot.
>Sorry, you missed the point. My example does indeed address the issue.
>(actually, to give credit where it's due, I should say that Prescott
>Turner first showed this example to me). To recall:
Oops, Sorry. You're right. I forgot my own previous text explaining
that ANSI-C defines argument promotion in terms of simple assignment.
> const char x = '?';
> char *p;
> const char **q = &p;
> *q = &x;
> *p = '!'; /* Oops! */
>The declaration of q is the problem and is therefore prohibited.
Your example looks strangely akin to the pointer aliasing problem.
Instead of the compiler losing control of whether a pointer was
modified, the compiler loses control of the qualifier type.
>Incidentally, I once asked Dave Prosser, the ANSI C project editor, why
>they decided not to allow the conversion. It turns out that they had
>not seen the example above, but couldn't prove it was safe so they
>prohibited it anyway.
It's better to err on the side of caution, but it turns out they
didn't err at all.
Thank you for taking time to explain the example and finding out
the history behind the restriction.
> --Andrew Koenig
> ark@europa.att.com
--
Quentin Barnes
qbarnes@urbana.mcd.mot.com | ..!uiucuxc!udc!qbarnes
Author: ark@alice.att.com (Andrew Koenig)
Date: 25 Oct 92 14:15:22 GMT Raw View
In article <1992Oct22.212802.839@urbana.mcd.mot.com> qbarnes@urbana.mcd.mot.com (Quentin Barnes) writes:
> Your example looks strangely akin to the pointer aliasing problem.
> Instead of the compiler losing control of whether a pointer was
> modified, the compiler loses control of the qualifier type.
Actually, I believe the key issue is this:
For any type T, a value of type T* is more powerful than a value of
type const T*, in the sense that anything I can do with the const T*
I can also do with the T*.
This is not true for T** vs. const T**, because I can use a const T**
value to modify a variable of type const T* but I cannot use a T**
variable that way. Thus neither T** nor const T** is more powerful
than the other, so free conversions between those two types cannot
be allowed.
> Thank you for taking time to explain the example and finding out
> the history behind the restriction.
You're welcome.
Incidentally, I conjecture that conversion from T** to const T * const *
(pointer to constant pointer to constant T) is always safe,
but I don't have a proof.
--
--Andrew Koenig
ark@europa.att.com
Author: mccauleyba@vax1.bham.ac.uk
Date: 29 Oct 92 19:55:46 GMT Raw View
In article <23999@alice.att.com>, ark@alice.att.com (Andrew Koenig) writes:
>
> Incidentally, I conjecture that conversion from T** to const T * const *
> (pointer to constant pointer to constant T) is always safe,
> but I don't have a proof.
There is another point that follows from all this when considering function
pointers. The current standard does not allow any promotion of function
pointers but can anyone say definitively if something like...
int strcmp(const char*,const char*);
int (*p)(char *,char*) = strcmp;
... could be safely allowed.
I can see no hazard in permitting conversion from (*)(const T*) to (*)(T*)
but then I hadn't seen the danger of converting T** to const T**.
Author: sdm@cs.brown.edu (Scott Meyers)
Date: Fri, 30 Oct 1992 02:12:07 GMT Raw View
In article <1992Oct29.195546.1@vax1.bham.ac.uk> mccauleyba@vax1.bham.ac.uk writes:
| There is another point that follows from all this when considering function
| pointers. The current standard does not allow any promotion of function
| pointers but can anyone say definitively if something like...
|
| int strcmp(const char*,const char*);
| int (*p)(char *,char*) = strcmp;
|
| ... could be safely allowed.
|
| I can see no hazard in permitting conversion from (*)(const T*) to (*)(T*)
| but then I hadn't seen the danger of converting T** to const T**.
I've been wondering about a similar question. Is there any danger of
allowing the "usual" conversions of pointers and references in parameter
types of function pointers? Example:
void f( int (*)(const void*, const void*) );
int g(const char*, const char*);
f( g ); // error
f (int (*)(const void*, const void*) g ); // fine
If the usual T* ==> void* conversion were allowed for function pointer
parameters, no cast would be required when calling f with g. Is there a
lurking danger here?
Scott
-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence? "Hello, Mr. Mayor."
Author: pat@frumious.uucp (Patrick Smith)
Date: 30 Oct 92 05:13:34 GMT Raw View
sdm@cs.brown.edu (Scott Meyers) writes:
|I've been wondering about a similar question. Is there any danger of
|allowing the "usual" conversions of pointers and references in parameter
|types of function pointers? Example:
|
| void f( int (*)(const void*, const void*) );
|
| int g(const char*, const char*);
|
| f( g ); // error
| f (int (*)(const void*, const void*) g ); // fine
|
|If the usual T* ==> void* conversion were allowed for function pointer
|parameters, no cast would be required when calling f with g. Is there a
|lurking danger here?
I think you have this the wrong way around. The example above is
dangerous. If f() is
void f( int (*h)(const void*, const void*) ) {
int x;
h( &x, &x );
}
you wind up converting an int* to a void* and then to a const char*.
Such a conversion should require an explicit cast at some point.
But if you do it the other way,
void f( int (*)(const char*, const char*) );
int g(const void*, const void*);
f( g ); // Should this be allowed?
there might not be any semantic problems.
However, if the conversion from const char* to const void* involves
any change to the actual representation of the pointer, then the
compiler may need to generate a small function to use as the
actual argument to f(). This function would just convert its
parameters from const char* to const void* and then call g.
--
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca
Author: mccauleyba@vax1.bham.ac.uk
Date: 30 Oct 92 18:43:41 GMT Raw View
In article <1992Oct30.051334.1016@frumious.uucp>, pat@frumious.uucp (Patrick Smith) writes:
> void f( int (*)(const char*, const char*) );
> int g(const void*, const void*);
>
> f( g ); // Should this be allowed?
>
> there might not be any semantic problems.
>
> However, if the conversion from const char* to const void* involves
> any change to the actual representation of the pointer, then the
> compiler may need to generate a small function to use as the
> actual argument to f(). This function would just convert its
> parameters from const char* to const void* and then call g.
There is a problem with this if g is also a ponter to a function as the
compiler would be required to create such a stub function for invokation of
f() this would mean having function pointers into the *stack*.
void f( int (*)(const char*, const char*) );
void h(int (*g)(const void*, const void*)) {
f( g ); // Should this be allowed?
}
I suppose it's possible that a compiler could even have different
representations for `const char *' and `char *' but I don't think that
many compiler writers would start jumping up and down if you told them that
they couldn't.
By the way even if *all* data pointers had the same internal represention this
would not solve the problem as convertion from a pointer to multiply derived
type, to a pointer to ones of it's base classes will unavoidably sometimes
require an adjustment.
\\ ( ) NO BULLSHIT! from BAM (Brian McCauley)
. _\\__[oo ============
.__/ \\ /\@
. l___\\ E-mail: B.A.McCauley@bham.ac.uk
# ll l\\
###LL LL\\
Author: sdm@cs.brown.edu (Scott Meyers)
Date: Sat, 31 Oct 1992 03:14:31 GMT Raw View
In article <1992Oct30.051334.1016@frumious.uucp> uunet.ca!frumious!pat writes:
| sdm@cs.brown.edu (Scott Meyers) writes:
| |I've been wondering about a similar question. Is there any danger of
| |allowing the "usual" conversions of pointers and references in parameter
| |types of function pointers? Example:
| |
| | void f( int (*)(const void*, const void*) );
| |
| | int g(const char*, const char*);
| |
| | f( g ); // error
| | f (int (*)(const void*, const void*) g ); // fine
| |
| |If the usual T* ==> void* conversion were allowed for function pointer
| |parameters, no cast would be required when calling f with g. Is there a
| |lurking danger here?
|
| I think you have this the wrong way around. The example above is
| dangerous. If f() is
|
| void f( int (*h)(const void*, const void*) ) {
| int x;
| h( &x, &x );
| }
|
| you wind up converting an int* to a void* and then to a const char*.
| Such a conversion should require an explicit cast at some point.
This seems like a program error. The caller is passing a function
expecting const char* arguments, but the callee is using it as if it is
expecting int* parameters. I'm interested in the case where the caller and
the callee agree on the types of what's being passed:
void f( int (*h)(const void*, const void*) ) {
h("Foo", "Bar");
}
Scott
-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence? "Hello, Mr. Mayor."
Author: pat@frumious.uucp (Patrick Smith)
Date: Sat, 31 Oct 1992 21:34:12 GMT Raw View
sdm@cs.brown.edu (Scott Meyers) writes:
|In article <1992Oct30.051334.1016@frumious.uucp> uunet.ca!frumious!pat writes:
|| sdm@cs.brown.edu (Scott Meyers) writes:
|| | void f( int (*)(const void*, const void*) );
|| |
|| | int g(const char*, const char*);
|| |
|| | f( g ); // error
|| | f (int (*)(const void*, const void*) g ); // fine
|| |
|| |If the usual T* ==> void* conversion were allowed for function pointer
|| |parameters, no cast would be required when calling f with g. Is there a
|| |lurking danger here?
||
|| If f() is
||
|| void f( int (*h)(const void*, const void*) ) {
|| int x;
|| h( &x, &x );
|| }
||
|| you wind up converting an int* to a void* and then to a const char*.
|| Such a conversion should require an explicit cast at some point.
|
|This seems like a program error. The caller is passing a function
|expecting const char* arguments, but the callee is using it as if it is
|expecting int* parameters. I'm interested in the case where the caller and
|the callee agree on the types of what's being passed:
|
| void f( int (*h)(const void*, const void*) ) {
| h("Foo", "Bar");
| }
Yes, the program using my version of f() is (probably) in error.
But when the compiler sees the call to f(), it doesn't (in the general
case) know which version of f() is being called. If it's your version,
there's no problem. If it's my version, there's probably something
wrong.
In my understanding, one of the principles of C++ is that any construct
which has the potential to violate the type system is illegal unless
an explicit cast is used. So the explicit cast should be required in
the call to f() - not because the compiler knows there is a problem,
just because it _doesn't_ know there _isn't_. This is exactly why
you need an explicit cast to convert a pointer to base class to
pointer to derived class, but not to cast from derived to base.
--
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca
Author: sdm@cs.brown.edu (Scott Meyers)
Date: Sun, 1 Nov 1992 20:15:47 GMT Raw View
In article <1992Oct31.213412.356@frumious.uucp> uunet.ca!frumious!pat writes:
| In my understanding, one of the principles of C++ is that any construct
| which has the potential to violate the type system is illegal unless
| an explicit cast is used. So the explicit cast should be required in
| the call to f() - not because the compiler knows there is a problem,
| just because it _doesn't_ know there _isn't_. This is exactly why
| you need an explicit cast to convert a pointer to base class to
| pointer to derived class, but not to cast from derived to base.
However, you don't need an explicit cast to go from a T* to a void*.
Hence, given that this is legal,
T *p;
void *q = p;
is there a good reason why this isn't?
void f(T*);
void (*pf)(void*) = f;
Scott
-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence? "Hello, Mr. Mayor."
Author: jbuck@forney.berkeley.edu (Joe Buck)
Date: 1 Nov 1992 23:17:47 GMT Raw View
In article <1992Nov1.201547.4661@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
>However, you don't need an explicit cast to go from a T* to a void*.
>Hence, given that this is legal,
>
> T *p;
> void *q = p;
>
>is there a good reason why this isn't?
>
> void f(T*);
> void (*pf)(void*) = f;
Certainly. Consider the line that would be legal next:
T2* p; // class T2 has no relation to class T
pf(p);
Now we have passed a pointer to T2 to a function that thinks it is getting
a pointer to T, and we have broken the type system.
It turns out that you've gotten things exactly backwards. It would
be safer (although there is still a problem) to have
void f(void*);
void (*pf)(T*) = f;
Why is this better? Because we can only pass T* pointers as arguments
to pf, and these are guaranteed to be legal to cast to void*. Why is
it still a problem? Because void* and T* may have different
representations.
--
Joe Buck jbuck@ohm.berkeley.edu
Author: sdm@cs.brown.edu (Scott Meyers)
Date: Mon, 2 Nov 1992 19:31:35 GMT Raw View
In article <1d1oirINN3vu@agate.berkeley.edu> jbuck@forney.berkeley.edu (Joe Buck) writes:
| In article <1992Nov1.201547.4661@cs.brown.edu> sdm@cs.brown.edu (Scott Meyers) writes:
| >Hence, given that this is legal,
| >
| > T *p;
| > void *q = p;
| >
| >is there a good reason why this isn't?
| >
| > void f(T*);
| > void (*pf)(void*) = f;
|
| Certainly. Consider the line that would be legal next:
|
| T2* p; // class T2 has no relation to class T
| pf(p);
|
| Now we have passed a pointer to T2 to a function that thinks it is getting
| a pointer to T, and we have broken the type system.
Boy, either I'm really dense or what we have here is a failure to
communicate. Clearly (I hope), if pf wants to do anything with its
argument, it has to know what the real type is, i.e., it's going to have to
do an explicit cast from void* to whatever the type really is. Obviously
this gives rise to the spector of type errors, but so does any use of void*
pointers. So my question is really whether there are any possibilities of
type errors other than those inherent in the use of void* pointers.
Scott
-------------------------------------------------------------------------------
What do you say to a convicted felon in Providence? "Hello, Mr. Mayor."
Author: pat@frumious.uucp (Patrick Smith)
Date: Tue, 3 Nov 1992 00:16:49 GMT Raw View
sdm@cs.brown.edu (Scott Meyers) writes:
|In article <1992Oct31.213412.356@frumious.uucp> uunet.ca!frumious!pat writes:
|| In my understanding, one of the principles of C++ is that any construct
|| which has the potential to violate the type system is illegal unless
|| an explicit cast is used. So the explicit cast should be required in
|| the call to f() - not because the compiler knows there is a problem,
|| just because it _doesn't_ know there _isn't_. This is exactly why
|| you need an explicit cast to convert a pointer to base class to
|| pointer to derived class, but not to cast from derived to base.
|
|However, you don't need an explicit cast to go from a T* to a void*.
Converting a T* to a void*, by itself, doesn't violate the type system,
because a void* can point to anything.
It's the conversion from a void* to a T* that might violate the type
system, because the void* might not point to a T. Hence the explicit
cast is required for this conversion.
|Hence, given that this is legal,
|
| T *p;
| void *q = p;
|
|is there a good reason why this isn't?
|
| void f(T*);
| void (*pf)(void*) = f;
Because it involves an implicit conversion of void* to T*
(_not_ T* to void*).
--
Patrick Smith
uunet.ca!frumious!pat
pat%frumious.uucp@uunet.ca
Author: qbarnes@urbana.mcd.mot.com (Quentin Barnes)
Date: Thu, 15 Oct 1992 22:20:18 GMT Raw View
In <23849@alice.att.com> ark@alice.att.com (Andrew Koenig) writes:
>I suppose I should give an example:
> const char x = '*';
> char* p;
>Now, if we could make p point at x, that would be bad news because we
>could then change the value of x even though it is a const object.
>Well, if we can do this:
> const char** q = &p;
>(which is illegal, but would be legal if conversions from T** to const T**
>existed), then we would be able to do this:
> *q = &x;
>which would effectively make p point at x. All the other operations here
>are unquestionably legal, which means we must rule out the declaration of q.
Sorry, you missed the point. I was refering to _argument_ promotion.
Your example does not address this issue.
Default argument promotion to "const" occurs all the time. In this
example, the pointer "p" is promoted to a type pointing to "const":
===
extern size_t strlen(const char *);
int z(char *p){
...
return( strlen(p) );
}
===
strlen() promises the caller that it will not modify the object pointed
to by p.
In this case:
===
void f(const char **);
z(){
char **p;
f(p); /* Illegal call */
}
===
f() would like to make the same promise that it will not modify the
object, but because of the ANSI-C argument promotion rules, it cannot.
I would like to know why this restriction was placed in ANSI-C. Is
it simply there to make the life of compiler writers easier?
> --Andrew Koenig
> ark@europa.att.com
--
Quentin Barnes
qbarnes@urbana.mcd.mot.com | ..!uiucuxc!udc!qbarnes
Author: ark@alice.att.com (Andrew Koenig)
Date: 18 Oct 92 15:17:54 GMT Raw View
In article <1992Oct15.222018.16565@urbana.mcd.mot.com> qbarnes@urbana.mcd.mot.com (Quentin Barnes) writes:
> Sorry, you missed the point. I was refering to _argument_ promotion.
> Your example does not address this issue.
> In this case:
> void f(const char **);
>
> z(){
> char **p;
> f(p); /* Illegal call */
> }
> f() would like to make the same promise that it will not modify the
> object, but because of the ANSI-C argument promotion rules, it cannot.
Sorry, you missed the point. My example does indeed address the issue.
(actually, to give credit where it's due, I should say that Prescott
Turner first showed this example to me). To recall:
const char x = '?';
char *p;
const char **q = &p;
*q = &x;
*p = '!'; /* Oops! */
The declaration of q is the problem and is therefore prohibited.
Now, there's nothing to stop us from putting that part into a function:
void f(const char **q, const char *xp)
{
*q = xp;
}
If we could call f with a first argument that is a char**, we could say
const char x = '?';
char *p;
f(&p, &x);
after which p points at x and we can break the type system by saying
*p = '!';
Therefore, we must prohibit the call f(&p, &x).
Incidentally, I once asked Dave Prosser, the ANSI C project editor, why
they decided not to allow the conversion. It turns out that they had
not seen the example above, but couldn't prove it was safe so they
prohibited it anyway.
--
--Andrew Koenig
ark@europa.att.com
Author: qbarnes@urbana.mcd.mot.com (Quentin Barnes)
Date: 8 Oct 92 16:26:53 GMT Raw View
In ANSI-C, the following code is not legal:
void f(const char **s);
void g()
{
char **s = 0;
f(s);
}
ANSI-C does not allow for implicit conversion of pointer of pointer
arguments. (See 3.3.2.2 for discussion of promotion of function
arguments. This case is one that does not hold true for 3.3.16.1).
Cfront 3.0.1 eats the non-ANSI-C code without blinking.
I can't find in the 23-sep-91 ANSI-C++ draft if pointer of pointer
promotion is allowed or disallowed. Can anyone point me to the
appropriate reference(s) so I know whether cfront is broken or not?
BTW, what's the latest ANSI-C++ draft?
--
Quentin Barnes
qbarnes@urbana.mcd.mot.com | ..!uiucuxc!udc!qbarnes
Author: ark@alice.att.com (Andrew Koenig)
Date: 9 Oct 92 04:55:41 GMT Raw View
In article <1992Oct8.162653.12028@urbana.mcd.mot.com> qbarnes@urbana.mcd.mot.com (Quentin Barnes) writes:
> I can't find in the 23-sep-91 ANSI-C++ draft if pointer of pointer
> promotion is allowed or disallowed. Can anyone point me to the
> appropriate reference(s) so I know whether cfront is broken or not?
Converting T** to const T** had better not be legal in C++, because if
it were, it would open a hole in the type system that would allow modification
of genuinely constant objects.
--
--Andrew Koenig
ark@europa.att.com
Author: ark@alice.att.com (Andrew Koenig)
Date: 9 Oct 92 14:14:10 GMT Raw View
In article <23842@alice.att.com>, I wrote:
> Converting T** to const T** had better not be legal in C++, because if
> it were, it would open a hole in the type system that would allow modification
> of genuinely constant objects.
I suppose I should give an example:
const char x = '*';
char* p;
Now, if we could make p point at x, that would be bad news because we
could then change the value of x even though it is a const object.
Well, if we can do this:
const char** q = &p;
(which is illegal, but would be legal if conversions from T** to const T**
existed), then we would be able to do this:
*q = &x;
which would effectively make p point at x. All the other operations here
are unquestionably legal, which means we must rule out the declaration of q.
--
--Andrew Koenig
ark@europa.att.com