Topic: Passing char ** as const char ** should be legal


Author: jr@efi.sintef.no (Jarand Roeynstrand)
Date: 3 Feb 1995 09:29:13 GMT
Raw View
In article <3grcvf$epv@news.uni-c.dk>, mojemj@unidhp.uni-c.dk (Mogens Jensen) writes:

[Stuff about Passing char ** as const char ** beeing illegal deleted]
|>
|> I agree that it is a strange restriction, why does it indicate a logic
|> error?, and what is the difference between [char*->const char*] and
|> [char**->const char**]?
|>
|> One could imagine that const meant anti-volatile, so that the compiler
|> was able to make certain presumptions (only load once); and a non-const
|> pointer could possibly violate this assumption - but this can't be the
|> case as [char*->const char*] is allowed (and we have the volatile
|> keyword) ...
|>

Consider this example, that is legal if the conversion [char**->const char**]
is legal:

void f( const char ** p)
{
    static const char *message= "Hello world!";
    *p = message;
}

main()
{
    char *p;
    f( &p );
    p[0] = 'F'; // Breaks constness of f::message
}

I hope this helps.

(This article is sent once already, but under wrong topic. Sorry)

--

   ====         Best regards
     \\                              Jarand Roeynstrand
      \\                           DIG-hackers department
     \====\
  .,;,\    \                     EFI
,;;;;;;;;,_/     ____          N-7034 Trondheim
           |    |              Norway
           |    |
            \__/

Phone:  +47-73 59 72 75           Email: jr@efi.sintef.no




Author: Per Angstrom <eri.edt.edtpang@memo.ericsson.se>
Date: 3 Feb 1995 15:55:30 GMT
Raw View
jr@efi.sintef.no (Jarand Roeynstrand) wrote:

> Consider this example, that is legal if the conversion [char**->const char**]
> is legal:
>
> void f( const char ** p)
> {
>     static const char *message= "Hello world!";
>     *p = message;
> }
>
> main()
> {
>     char *p;
>     f( &p );
>     p[0] = 'F'; // Breaks constness of f::message
> }
>
> I hope this helps.

OK. That helped a lot. I hadn't though of that.
But I won't give up; instead I will restate my claim:

!!!  Passing 'char **' as 'const char * const *' should be legal !!!

For proof of this, change the signature of function f to

 void f( const char * const * p )

Then you won't be able to do the assignment to *p, and you
will have covered the hole in the type system.
(Until somebody discovers a new hole!)





Author: jason@cygnus.com (Jason Merrill)
Date: Fri, 3 Feb 1995 21:15:42 GMT
Raw View
>>>>> Per Angstrom <eri.edt.edtpang@memo.ericsson.se> writes:

> But I won't give up; instead I will restate my claim:

> !!!  Passing 'char **' as 'const char * const *' should be legal !!!

And it is.

  4.4  Qualification conversions                             [conv.qual]

1 An rvalue of type pointer to cv1 T can be converted to  an  rvalue  of
  type  pointer to cv2 T if cv2 T is more cv-qualified than cv1 T.  When
  a multi-level pointer is composed of data member pointers, or a mix of
  object  and data member pointers, the rules for adding type qualifiers
  are the same as those for object pointers.  That  is,  the  ``member''
  aspect  of the pointers is irrelevant in determining where type quali-
  fiers can be added.

2 A conversion can add type qualifiers at levels other than the first in
  multi-level pointers, subject to the following rules:3)
    Two pointer types T1 and T2 are similar if there exists a type T and
    integer N>0 such that:

            T1isTcv1,n*...cv1,1*cv1,0
    and

            T2isTcv2,n*...cv2,1*cv2,0
    where each cvi,j is const, volatile, const volatile, or nothing.  An
    expression of type T1 can be converted to type T2 if and only if the
    following conditions are satisfied:

      --the pointer types are similar.
  _________________________
  2) This conversion never applies to member functions because there  is
  no way to obtain an lvalue for a member function.
  3)  These  rules  ensure that const-safety is preserved by the conver-
  sion.  This conversion never applies to nonstatic member functions be-
  cause there is no way to obtain an lvalue for a nonstatic member func-
  tion.

      --for  every j>0, if const is in cv1,j then const is in cv2,j, and
        similarly for volatile.

      --the cv1,j and cv2,j are different, then const is in every  cv2,k
        for 0<k<j.

3 An  rvalue  of  type  pointer to member of X of type cv1 T can be con-
  verted to an rvalue of type pointer to member of X of  type  cv2 T  if
  cv2 T is more cv-qualified than cv1 T.




Author: mojemj@unidhp.uni-c.dk (Mogens Jensen)
Date: 4 Feb 1995 03:08:11 GMT
Raw View
Per Angstrom (eri.edt.edtpang@memo.ericsson.se) wrote:
: jr@efi.sintef.no (Jarand Roeynstrand) wrote:

: > Consider this example, that is legal if the conversion
: > [char**->const char**] is legal:
: >
: > void f( const char ** p)
: > {
: >     static const char *message= "Hello world!";
: >     *p = message;
: > }
: >
: > main()
: > {
: >     char *p;
: >     f( &p );
: >     p[0] = 'F'; // Breaks constness of f::message
: > }
: >
: > I hope this helps.

Hey, Great! That explains it. I was very puzzled about this, but of
course you are right!


: OK. That helped a lot. I hadn't though of that.
: But I won't give up; instead I will restate my claim:

: !!!  Passing 'char **' as 'const char * const *' should be legal !!!

It *is* !

(Using Borland C++ 4.02 or IBM C++ for OS/2)


Greetings from Jens Jakob Jensen, Denmark - (Using my fathers account)




Author: tob@world.std.com (Tom O Breton)
Date: Sat, 4 Feb 1995 04:30:52 GMT
Raw View
Per Angstrom <eri.edt.edtpang@memo.ericsson.se> writes:
[ why doesn't char** assign to const char **? ]

Because then constness could be accidentally removed:

const   int     **      A;
        int     **      B;
        A = B;  //The conversion you want.
        assert( *A == *B );

const   int     *       C = 0;
        *A = C;
        assert( C == *B );
        *B = 1;
        assert( C == 1 );

        Tom

--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower





Author: tob@world.std.com (Tom O Breton)
Date: Sat, 4 Feb 1995 04:30:54 GMT
Raw View
Per Angstrom <eri.edt.edtpang@memo.ericsson.se> writes:
[ why doesn't char** assign to const char **? ]

Because then constness could be accidentally removed:

const   int     **      A;
        int     **      B;
        A = B;  //The conversion you want.
        assert( *A == *B );

const   int     *       C = 0;
        *A = C;
        assert( C == *B );
        *B = 1;
        assert( C == 1 );

        Tom

--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower





Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Sat, 4 Feb 1995 16:59:02 GMT
Raw View
Per Angstrom <eri.edt.edtpang@memo.ericsson.se> writes:

>But I won't give up; instead I will restate my claim:
>
>!!!  Passing 'char **' as 'const char * const *' should be legal !!!

It is!

Note that allowing this is a relatively recent change, and some
compilers have not yet implemented it.

--
Fergus Henderson - fjh@munta.cs.mu.oz.au
all [L] (programming_language(L), L \= "Mercury") => better("Mercury", L) ;-)




Author: pat@tesuji.qc.ca (Patrick Smith)
Date: Sat, 4 Feb 1995 19:25:23 GMT
Raw View
Per Angstrom <eri.edt.edtpang@memo.ericsson.se> writes:
>!!!  Passing 'char **' as 'const char * const *' should be legal !!!

It is, in the committee's working papers.

Compilers may take a while to catch up.


--
Patrick Smith
pat@tesuji.qc.ca




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 5 Feb 1995 19:45:18 GMT
Raw View
In article <3gtjli$lsg@erinews.ericsson.se> Per Angstrom <eri.edt.edtpang@memo.ericsson.se> writes:
>jr@efi.sintef.no (Jarand Roeynstrand) wrote:
>
>> Consider this example, that is legal if the conversion [char**->const char**]
>> is legal:

 It isnt. For the reason you give. (It isn't safe)
>
>!!!  Passing 'char **' as 'const char * const *' should be legal !!!

 It is. Because, as you claim, it is safe.
>
>For proof of this, change the signature of function f to
>
> void f( const char * const * p )

 That is a demonstration, not a proof.

>(Until somebody discovers a new hole!)

 There's no hole. ISO C does not permit the safe
conversion

 char** --> char const* const*

whereas C++ now does. And other similar conversions, see the
Working Paper for a specification. The proof that the
conversions permitted in the WP are safe is due to Pat Smith.

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,
        81A Glebe Point Rd, GLEBE   Mem: SA IT/9/22,SC22/WG21
        NSW 2037, AUSTRALIA     Phone: 61-2-566-2189




Author: Per Angstrom <eri.edt.edtpang@memo.ericsson.se>
Date: 2 Feb 1995 14:35:28 GMT
Raw View
Consider the following piece of code:


Sample Code
--------------------------------
extern "C" {
char ** old_C_function();
};

void funcA( const char ** )
{
}

void funcB( const char * )
{
}

int main()
{
 char ** strings = old_C_function();

 funcA( strings );       // line 17: warning
 funcB( strings[0] );    // line 18: OK

 return 0;
}


Compiler Output
----------------------
When compiling the program I get the following warning from Sun CC 4.0:

>> "atest.cc", line 17: Warning (Anachronism): Formal argument 1 of
>> type const char** in call to funcA(const char**) is being
>> passed char**.
>> "atest.cc", line 17: Note: Type "CC -migration" for more on
>> anachronisms.
>> 1 Warning(s) detected.


Explanation from the Compiler
-------------------------------------
"CC -migration" gives the following explanation:

>> 1.6.13  Formal argument AAAA of type BBBB is being passed CCCC
>>                         or
>>         Formal argument AAAA of type BBBB in call to CCCC is being passed
>>                         DDDD

>> These warnings indicate that the conversion required to pass the
>> argument is illegal, but that C++ 3.0 did not detect the
>> illegality. Failure to handle const and volatile properly on
>> pointers is a major cause of this problem.
>> You can usually correct it with an explicit cast, but the error is
>> probably the result of a logic error. Passing a value of type
>> char** to an argument of type const char** is illegal in both
>> C++ and ANSI C. This is not an error or oversight in the standard
>> as such assignments open a hole in the type system and violate
>> const safety.


My Opinion
-----------------------------------------------
IMHO, this is too strict. I do agree that implicit conversion _from_
const to non-const should not be done, but I cannot see how implicit
conversion _to_ const could do any harm.

I see three (bad) ways to avoid getting this warning:

1) Of course, I can do an explicit cast, but that I think is an even
bigger hole in the type system, as long as my compiler does not
support the const_cast operator.
2) I can say 'void funcA( char ** );', which will give me a problem when
I want to pass it const char ** arguments.
3) I can modify the signature of old_C_function to return const char **,
but in real life this declaration might be in a header file which I do
not want to tamper with.


My Questions
-----------------------------------------------------------
My questions are: Am I missing something here? Have I not found the right
passage in the holy book? Will somebody please enlighten me?

I also wonder, if the standard is to be consistent, why is line 18 OK?


Post Scriptum
-----------------------------------------
This is not a complaint on Sun CC. Borland C++ 4.0 gives an error
instead of a warning.







Author: mojemj@unidhp.uni-c.dk (Mogens Jensen)
Date: 2 Feb 1995 19:49:03 GMT
Raw View
Per Angstrom (eri.edt.edtpang@memo.ericsson.se) wrote:
: Consider the following piece of code:


: Sample Code
: --------------------------------
: extern "C" {
: char ** old_C_function();
: };

: void funcA( const char ** )
: {
: }

: void funcB( const char * )
: {
: }

: int main()
: {
:  char ** strings = old_C_function();

:  funcA( strings );       // line 17: warning
:  funcB( strings[0] );    // line 18: OK
:
:  return 0;
: }


: Compiler Output
: ----------------------
: When compiling the program I get the following warning from Sun CC 4.0:

: >> "atest.cc", line 17: Warning (Anachronism): Formal argument 1 of
: >> type const char** in call to funcA(const char**) is being
: >> passed char**.
: >> "atest.cc", line 17: Note: Type "CC -migration" for more on
: >> anachronisms.
: >> 1 Warning(s) detected.


: Explanation from the Compiler
: -------------------------------------
: "CC -migration" gives the following explanation:

: >> 1.6.13  Formal argument AAAA of type BBBB is being passed CCCC
: >>                         or
: >>         Formal argument AAAA of type BBBB in call to CCCC is being passed
: >>                         DDDD

: >> These warnings indicate that the conversion required to pass the
: >> argument is illegal, but that C++ 3.0 did not detect the
: >> illegality. Failure to handle const and volatile properly on
: >> pointers is a major cause of this problem.
: >> You can usually correct it with an explicit cast, but the error is
: >> probably the result of a logic error. Passing a value of type
: >> char** to an argument of type const char** is illegal in both
: >> C++ and ANSI C. This is not an error or oversight in the standard
: >> as such assignments open a hole in the type system and violate
: >> const safety.


: My Opinion
: -----------------------------------------------
: IMHO, this is too strict. I do agree that implicit conversion _from_
: const to non-const should not be done, but I cannot see how implicit
: conversion _to_ const could do any harm.

: I see three (bad) ways to avoid getting this warning:

: 1) Of course, I can do an explicit cast, but that I think is an even
: bigger hole in the type system, as long as my compiler does not
: support the const_cast operator.
: 2) I can say 'void funcA( char ** );', which will give me a problem when
: I want to pass it const char ** arguments.
: 3) I can modify the signature of old_C_function to return const char **,
: but in real life this declaration might be in a header file which I do
: not want to tamper with.


4) You can use function overloading and make two versions of funcA:
 void funcA(char**), funcA(const char**);

I agree that it is a strange restriction, why does it indicate a logic
error?, and what is the difference between [char*->const char*] and
[char**->const char**]?

One could imagine that const meant anti-volatile, so that the compiler
was able to make certain presumptions (only load once); and a non-const
pointer could possibly violate this assumption - but this can't be the
case as [char*->const char*] is allowed (and we have the volatile
keyword) ...

I am puzzled...


: Post Scriptum
: -----------------------------------------
: This is not a complaint on Sun CC. Borland C++ 4.0 gives an error
: instead of a warning.


Greetings from Jens Jakob Jensen, Denmark - (Using my fathers account)