Topic: Constructing an array of class-type element


Author: jimad@microsoft.com (Jim Adcock)
Date: 20 Aug 93 20:41:45 GMT
Raw View
In article <rfgCBtI4B.GEK@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:


|>>         "A simple-type-name (7.1.6) followed by an (empty) pair of paren-
|>>         theses constructs a value of the specified type.  If the type is
|>>         a class with a suitably declared constructor that constructor will
|>>         be called; otherwise the result is an undefined value of the speci-
|>>         fied type."

Perhaps someone else can comment on whether it was really intended
that a simple-type-name include array types [via typedef] or is this
simply a weakness in the specification of the grammar in the ARM ?

| "If the type is a class with a suitably declared constructor, that
| constructor will be called; otherwise the result is an undefined
| value of the specified type."
|
|Now in the case shown above, type type `T' is definitely *not* a class type.
|Rather, it is an array type.  So execution of `sp = T();' must create a
|value of the given array type, and that value must have "an undefined value".
|
|But wait!  That would seem to violate the general principal that the language
|never allows the creation of an object (or several objects, as in this case)
|of a given class type which HAS it own constructor(s) without invoking one
|of the constructors for each object of the given type which is ever created.
|
|Summary:  I have posted a hunk of perfectly valid C++ code which, according
|to the rules stated in the ARM, *must* necessarily violate one of the most
|fundamental universal rules of the language.
|
|In short, the example I posted would seem to illustrate a serious "bug" in
|the current language definition.

A bug? Maybe.  Serious?  No.  Either simple-type-name can be refined
to clearly exclude arrays, array of class objects can be added as things
for which constructors are called, or the situation could even be left as is.
Leaving things as-is wouldn't even be a serious problem because this
issue certainly has not reared its ugly head often in the past.





Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Sun, 15 Aug 1993 20:26:34 GMT
Raw View
In article <1993Aug6.090138.635@ericsson.se> ehsjony@zeus.ericsson.se writes:
>In article 565@netcom.com, rfg@netcom.com (Ronald F. Guilmette) writes:
>> I have tried several C++ compilers, and none of them seem to do what I
>> think (intutively) is the Right Thing with the following small test
>> program.
>>
>> As far as I know, C++ compilers should compile this code without complaint,
>> and the resulting executable, when executed, should exit quietly with a
>> zero exit status.
>>
>> Has anyone got a C++ compiler which does the Right Thing with this example?
>>
>> -- rfg
>>
>>
>> ============================================================================
>> /* (C) 1993 Ronald F. Guilmette; all rights reserved.  */
>>
>> /* Section 5.2.3; C++ ARM:
>>
>>         "A simple-type-name (7.1.6) followed by an (empty) pair of paren-
>>         theses constructs a value of the specified type.  If the type is
>>         a class with a suitably declared constructor that constructor will
>>         be called; otherwise the result is an undefined value of the speci-
>>         fied type."
>>
>>    Check that an array type value whose element type is a class type (with
>>    an explicit default constructor) may be constructed using this notation.
>> */
>>
>> #include <stdio.h>
>>
>> int err_count = 0;
>> int ctor_count = 0;
>>
>> struct S
>> {
>>     S () { ctor_count++; }
>> };
>>
>> typedef S T[3];
>>
>> S *sp;
>>
>> int
>> test ()
>> {
>>    sp = T();
>
>I do not understand this line ->   sp = T(); and neither does Sun`s CC

Well, then that (cfront based) implementation has a bug... just like most
of the other C++ compilers I have tried.

Since this *is* the comp.std.c++ newsgroup, it would probably be a good
idea to try to focus on what the ARM and/or the current X3J16 working
papers say rather than wasting too much time talking about specific bugs
in specific implementations (or about our intutive notions of what is or
isn't "right code").

Quoting from the ARM, section 5.2.3:

 "A simple-type-name (7.1.6) followed by a (empty) pair of parentheses
 constructs a value of the specified type."

Elsewhere (17.3) `simple-class-name' is defined (syntactically) as (among
other things) a `qualified-type-name' and a `qualified-type-name' is defined
(syntactically) as (among other things) a `typedef-name'.

In short, the above code is completely valid syntactically, and I know of
no rule which would render it INVALID semantically.  Thus, I must conclude
that it is perfectly valid C++ code.

Now the only question is "What does it do when executed?"

Again, quoting from 5.3.2:

 "If the type is a class with a suitably declared constructor, that
 constructor will be called; otherwise the result is an undefined
 value of the specified type."

Now in the case shown above, type type `T' is definitely *not* a class type.
Rather, it is an array type.  So execution of `sp = T();' must create a
value of the given array type, and that value must have "an undefined value".

But wait!  That would seem to violate the general principal that the language
never allows the creation of an object (or several objects, as in this case)
of a given class type which HAS it own constructor(s) without invoking one
of the constructors for each object of the given type which is ever created.

Summary:  I have posted a hunk of perfectly valid C++ code which, according
to the rules stated in the ARM, *must* necessarily violate one of the most
fundamental universal rules of the language.

In short, the example I posted would seem to illustrate a serious "bug" in
the current language definition.  (Note that the current X3J16 draft working
paper is NOT in any way different from the ARM with regard to the rules
applicable to the code shown above.)

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: luj@ecn.purdue.edu (John Lu)
Date: Mon, 16 Aug 1993 06:26:37 GMT
Raw View
In article <rfgCBtI4B.GEK@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:

|> [ deleted ...]
|> >>
|> >> typedef S T[3];
|> >>
|> >> S *sp;
|> >>
|> >> int
|> >> test ()
|> >> {
|> >>    sp = T();
|> >
|> >I do not understand this line ->   sp = T(); and neither does Sun`s CC
|>
|> [ deleted ...]
|>
|> Quoting from the ARM, section 5.2.3:
|>
|>  "A simple-type-name (7.1.6) followed by a (empty) pair of parentheses
|>  constructs a value of the specified type."
|>
|> Elsewhere (17.3) `simple-class-name' is defined (syntactically) as (among
|> other things) a `qualified-type-name' and a `qualified-type-name' is defined
|> (syntactically) as (among other things) a `typedef-name'.
|>
|> In short, the above code is completely valid syntactically,

Agree.

|> and I know of
|> no rule which would render it INVALID semantically.

Semantically it is _undefined_ if you don't want to
call it invalide. See below.

|> Thus, I must conclude
|> that it is perfectly valid C++ code.
|>
|> Now the only question is "What does it do when executed?"
|>
|> Again, quoting from 5.3.2:
|>
|>  "If the type is a class with a suitably declared constructor, that
|>  constructor will be called; otherwise the result is an undefined
|>  value of the specified type."
|>
|> Now in the case shown above, type type `T' is definitely *not* a class type.
|> Rather, it is an array type.  So execution of `sp = T();' must create a
|> value of the given array type, and that value must have "an undefined value".
|>

ARM (8.2.4, p136):
 "When an identifier of array type appears in an expression,
 except as the operand of sizeof or & or used to initialize
 a reference(8.4.3), it is converted into a pointer to
 the first memeber of the array."

Therefore, in the expression of "T()" in your test program, T is a _pointer_
to S. i.e. T is a _simple_ type. Hence the result is _undefined_
according to your quote fo ARM  5.3.2.

(Futher in the commetary on page 137, "The C notiona of an array -- which C++
has adopted without change -- is very low-level.")


|> [deleted ...]

--
-- John Lu                              | INTERNET:luj@{ecn,cs}.purdue.edu --
-- Civil Engieering & Computer Sciences | UUCP:    {pur-ee, purdue}!luj    --
-- Purdue University                    | PHONE:   317-494-2234            --
-- West Lafayette, IN 47907-1208        | FAX:     317-496-1105            --




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Mon, 16 Aug 1993 17:01:17 GMT
Raw View
In article <CBu9wD.Lzo@noose.ecn.purdue.edu> luj@ecn.purdue.edu writes:
>In article <rfgCBtI4B.GEK@netcom.com>, rfg@netcom.com (Ronald F. Guilmette) writes:
>
>|> [ deleted ...]
>|> >>
>|> >> typedef S T[3];
>|> >>
>|> >> S *sp;
>|> >>
>|> >> int
>|> >> test ()
>|> >> {
>|> >>    sp = T();
>|> >
>|> >I do not understand this line ->   sp = T(); and neither does Sun`s CC
...
>|> ... I know of no rule which would render it INVALID semantically.
...
>Semantically it is _undefined_ if you don't want to
>call it invalid. See below.
...
>ARM (8.2.4, p136):
> "When an identifier of array type appears in an expression,
> except as the operand of sizeof or & or used to initialize
> a reference(8.4.3), it is converted into a pointer to
> the first memeber of the array."
>
>Therefore, in the expression of "T()" in your test program, T is a _pointer_
>to S. i.e. T is a _simple_ type. Hence the result is _undefined_
>according to your quote fo ARM  5.3.2.

Your analysis, although incorrect, serves to illustrate a couple of serious
problems in the section of the ARM which you have quoted.

In the first place, it is quite completely apparent (to students of the
ANSI C standard at least) that Stroustrup goofed when he wrote the ARM
rule which you have quoted.  I'm quite sure that he did not mean to say
"an identifier of array type".  Rather, what was intended was something
more like:

 "Each EXPRESSION or SUBEXPRESSION which (initially) has some array
 type (e.g. T[N] or T[]) and which appears in some context OTHER
 THAN as the operand of the `sizeof' or unary `&' operator (or as
 the initializer expression for an object declared to have some
 reference type) is implicitly converted to to a value of type T*.
 The resulting T* value (after this conversion) is a pointer to
 the first element of the original array object, and is not an
 lvalue."

This kind of implicit type conversion is known as "array type decay" (to
students of C).  Array type decay is most definitely NOT something which
occurs to TYPENAMES.  It is a process which occurs on EXPRESSION.  (There
*IS* a big difference.)

So your assertion that for `T()' (in my little example) the name `T' itself
goes through type decay is just not correct.  This is NOT the way things
work.  (Note however that the *value* of the complete EXPRESSION `T()' does
indeed decay into a pointer-type value, after it has been yielded.)

So there you have the first problem (with the ARM).  It says "identifier"
when it really should have said "expression".

The second problem with the ARM is more subtle, and this second problem
also afflicts the ANSI/ISO C standard as well.  (As a matter of fact, this
point has come up in discussion not that long ago in the comp.std.c news
group.)

The problem is that both the ANSI/ISO C standard *and* current X3J16 working
papers talk about "undefined behavior" (on the one hand) *and* (separately)
about "undefined values".  The fact that both of these terms are used (in
different places) is not, in and of itself, a problem, but both the ANSI/ISO
C standard *and* the ANSI/ISO C++ standard (when we have one) really ought
to clearly elucidate the differences between these two terms.

"Undefined behavior" can mean anything.  It could mean that the sun goes
nova, and/or that the monitor you are looking at implodes and showers you
with shards of broken glass.  "Undefined value" however is a more restricted
form of undefined behavior.  A program which generates an undefined value
is expected to continue on without (for example) totally erasing your disk.
If you then try to USE the undefined value, you may get undefined behavior,
but just storing it, taking its address, or using `sizeof' on it should not
cause you to enter into the realm of undefined behavior.

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: ehsjony@zeus.ericsson.se (Jonas Nygren)
Date: Fri, 6 Aug 1993 09:01:38 GMT
Raw View
In article 565@netcom.com, rfg@netcom.com (Ronald F. Guilmette) writes:
> I have tried several C++ compilers, and none of them seem to do what I
> think (intutively) is the Right Thing with the following small test
> program.
>
> As far as I know, C++ compilers should compile this code without complaint,
> and the resulting executable, when executed, should exit quietly with a
> zero exit status.
>
> Has anyone got a C++ compiler which does the Right Thing with this example?
>
> -- rfg
>
>
> ============================================================================
> /* (C) 1993 Ronald F. Guilmette; all rights reserved.  */
>
> /* Section 5.2.3; C++ ARM:
>
>         "A simple-type-name (7.1.6) followed by an (empty) pair of paren-
>         theses constructs a value of the specified type.  If the type is
>         a class with a suitably declared constructor that constructor will
>         be called; otherwise the result is an undefined value of the speci-
>         fied type."
>
>    Check that an array type value whose element type is a class type (with
>    an explicit default constructor) may be constructed using this notation.
> */
>
> #include <stdio.h>
>
> int err_count = 0;
> int ctor_count = 0;
>
> struct S
> {
>     S () { ctor_count++; }
> };
>
> typedef S T[3];
>
> S *sp;
>
> int
> test ()
> {

I do not understand this line ->   sp = T(); and neither does Sun`s CC

This is how I would have written it

  T t;
  sp = &t[0];

and Sun's CC produces the right code with these lines

>   if (ctor_count != 3)
>     {
>       printf ("wrong # of elements constructed: %d\n", ctor_count);
>       err_count++;
>     }
>
>   return err_count;
> }
>
> int main () { return test (); }
> --
>
> -- Ronald F. Guilmette ------------------------------------------------------
> ------ domain address: rfg@netcom.com ---------------------------------------
> ------ uucp address: ...!uunet!netcom.com!rfg -------------------------------