Topic: Defining members of class T within cla


Author: kanze@lts.sel.alcatel.de (James Kanze US/ESC 60/3/141 #40763)
Date: 1995/08/09
Raw View
In article <40834p$lm3@engnews2.Eng.Sun.COM> clamage@Eng.Sun.COM
(Steve Clamage) writes:

|> Adding a module system to C++ would make it generally easier to use,
|> particularly for large projects, but would also make it a different language.
|> Stroustrup elected not to go that route in designing and developing C++, and
|> the C++ committee also elected not to go that route.

|> The committee felt it had more than enough work to do without adding
|> another huge pile of work. Figuring out a module system, or some subset
|> that would ease separation of interface and implementation, would take
|> years of experimentation in addition to all the work that has been done
|> and is still being done on the standard. For example, the template rules
|> were published in the ARM in 1990. The rules are still being worked on
|> as new problems with the definition of templates are discovered.

|> I'd like to suggest that someone take the time to define and implement
|> something like a module system for C++. To gain wide acceptance, it should
|> allow existing C++ source and object code to be integrated into Modular C++
|> (if I may coin the term) without modification, as well as provide the same C
|> compatibility as C++ currently has. When you are done, submit it for the next
|> round of C++ standardization. The timing should be about right.

It's too late now, but I wonder if having a module system of some sort
wouldn't have made the template problem easier.

I suppose that the problem Bjarne faced was that doing generic classes
with #define is an even greater pain than doing separate compilation
with textual inclusion.  So the interest in having templates was
greater.  (If you really want to go batty, try implementing the ideas
in Barton and Nackman on a compiler which doesn't support templates.)
--
James Kanze         Tel.: (+33) 88 14 49 00        email: kanze@gabi-soft.fr
GABI Software, Sarl., 8 rue des Francs-Bourgeois, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                              -- Beratung in industrieller Datenverarbeitung







Author: thp@PROBLEM_WITH_INEWS_DOMAIN_FILE (Tom Payne)
Date: 1995/08/09
Raw View
Steve Clamage (clamage@Eng.Sun.COM) wrote:
: In article 1427@ittpub, wil@ittpub.nl (Wil Evers) writes:
: >
: >IMHO this is one of the major drawbacks of the language as currently
: >defined. Having to textually list *all* the members of a class, including
: >the private ones, before the place an object of that class is instantiated
: >makes it hard to have a clean separation between interface and
: >implementation. We either have to live with the extra dependencies of the
: >user code on the private class members or declare a separate 'physical
: >representation' class whose instances are usually allocated on the heap,
: >which slows down the program.

: All true.

: >For quite a few cases, it can be shown that a mechanism can be implemented
: >that would allow for the instantiation of objects whose full definition
: >has not yet been seen by the compiler, for instance because it is in a
: >different translation unit.

: In fact, other languages are defined that way, so this is not just a
: theorectical observation. Ada, Turbo Pascal, and Modula-2 are examples.

: >Has the committee considered any extensions to the current language
: >definition that would allow for such a mechanism? If so, why don't they
: >appear in the draft standard? If not so, why not?

: The original intention of C++ was to use the C compilation model, and to
: allow as much compatibility with C source and object code as possible.


By way of terminology, let's say that a type "depends" on the types of
its members, bases, and elements.  It requires two passes to
topologically sort the graph of this dependency relation:

  First pass:   attach to each node its in-degree and put the nodes
                of in-degree zero into a special set;

  Second pass:  while ( the graph has nodes of in-degree zero ) {
                   remove one from the graph;
                   update the in-degrees of its neighbors;
                   add to the set the neighbors with in-degree zero;
                }

A compiler can process type definitions in order of removal.  There
will be no left-over nodes if (and only if) dependency is well
founded, i.e. its graph is acyclic.

The current C++ requirement, that type definitions be completed before
being used to declare members, essentially requires the programmer to
topologically sort the source files, work that a compiler can and
should do for itself.  There would be no issues of backward
compatibility (with C or with earlier versions of C++) and no required
changes to linkers or header files, if this requirement were relaxed to
simply requiring that dependency be well founded.


Tom Payne (thp@cs.ucr.edu)








Author: wil@ittpub.nl (Wil Evers)
Date: 1995/08/10
Raw View
In article <40834p$lm3@engnews2.Eng.Sun.COM> clamage@Eng.Sun.COM (Steve
Clamage) writes:

> Adding a module system to C++ would make it generally easier to use,
> particularly for large projects, but would also make it a different
language.
> Stroustrup elected not to go that route in designing and developing C++,
and
> the C++ committee also elected not to go that route.
>
> The committee felt it had more than enough work to do without adding
> another huge pile of work. Figuring out a module system, or some subset
> that would ease separation of interface and implementation, would take
> years of experimentation in addition to all the work that has been done
> and is still being done on the standard. For example, the template rules
> were published in the ARM in 1990. The rules are still being worked on
> as new problems with the definition of templates are discovered.
>
> I'd like to suggest that someone take the time to define and implement
> something like a module system for C++. To gain wide acceptance, it
should
> allow existing C++ source and object code to be integrated into Modular
C++
> (if I may coin the term) without modification, as well as provide the
same C
> compatibility as C++ currently has. When you are done, submit it for the
next
> round of C++ standardization. The timing should be about right.

Because we already have templates, we may be closer to Modular C++ than
some people think. After all, there isn't much of a difference between

template <class T> void swap(T& t1, T& t2)
 { T tmp = t1; t1 = t2; t2 = tmp; }

and

void swap(SomePartiallySeenClass& t1, SomePartiallySeenClass& t2)
 { SomePartiallySeenClass tmp = t1; t1 = t2; t2 = tmp; }

When parsing the first example, the compiler will have to remember swap's
definition in some half-compiled format and fill in the missing
information when swap() is called for a particular type, that is, when T
is fully defined. Note that swap()'s definition may be in one translation
unit and the call to swap() in another.
This is almost exactly what should happen to the second swap() in Modular
C++ when the full definition of SomePartiallyDefinedClass is seen, the
difference being that the second swap() should be instantiated for one
type only.

Actually, it wouldn't surprise me if some programmers are already using
templates to separate interface and implementation, as in:

template <class Details> class DetailsSpecifiedElsewhere {
public :
 // whatever needs to go here
 ...
private :
 Details details;
};

Once this definition is seen, all classes and functions instantiating
objects of the DetailsSpecifiedElsewhere class could be expressed as
templates taking an argument to specify what details to use, waiting to
become fully functional when the details are fully defined and stuffed
into some DetailsSpecifiedElsewhere object.

This, of course, would be a gross abuse of the template mechanism. It
suggests that different sets of implementation details for the
DetailsSpecifiedElsewhere class could be used, which is definitely not
what's intended. However, it does suggest that a mechanism to separate
interface and implementation may already be in place.

- Wil





Author: hbaker@netcom.com (Henry Baker)
Date: 1995/08/07
Raw View
In article <401l3v$eri@engnews2.Eng.Sun.COM>, clamage@Eng.Sun.COM (Steve
Clamage) wrote:

> hbaker@netcom.com (Henry Baker) writes:
>
> >So, since the structure I was defining did not 'contain' a member of the
> >class being defined,

In fact, ARM 9.7:

"Note that simply declaring a class nested in another does _not_ mean
that the enclosing class _contains_ an object of the enclosed class.
Nesting expresses scoping, _not containment_ of sub-objects."  [Emphasis
supplied.]

> Yes it did. The inner struct had a member of an incomplete type.
>
> >class pair
> >{class pair_rep {pair car,cdr;};
> > pair_rep *rep;};                // Only needs to know sizeof(pointer).
>
> Class pair_rep has members of type pair, but type pair is not a
> complete type at that point in the source code. The problem is not
> the pointer to pair_rep, but the definition of pair_rep itself.
>
> Another example, without any cross-referencing:
>
> class A;
>
> class B {
>     A a; // invalid, since A is not a complete type
> };
>
>
> It doesn't matter whether you use class B or not. Its definition
> is invalid. The definition of class B must appear after that of
> class A, or be removed entirely. In your example, you have to define
> the nested class pair_rep after the definition of pair, not inside it.

But what about the ANSI/ISO resolution, 9.7, page 421:

"Like a member function, a nested class may be declared within the
enclosing class and defined elsewhere in the same or containing scope,
using the qualified name for the class definition:..."

[Example showing what I would call 'rewriting' semantics for nested classes.]

One might reasonably infer that 'like a member function' implied that the same
sort of 'rewriting equivalence' exists for nested classes as well as member
function bodies.

Since this is an entirely satisfactory interpretation, which eliminates
the necessity of many unpleasant explicit rewritings, I would argue that
the ANSI/ISO standard adopt this reading of these passages regarding
nested class declarations.

--
www/ftp directory:
ftp://ftp.netcom.com/pub/hb/hbaker/home.html





Author: clamage@Eng.Sun.COM (Steve Clamage)
Date: 1995/08/07
Raw View
In article 0608952350520001@192.0.2.1, hbaker@netcom.com (Henry Baker) writes:
>In article <401l3v$eri@engnews2.Eng.Sun.COM>, clamage@Eng.Sun.COM (Steve
>Clamage) wrote:
>
>> >class pair
>> >{class pair_rep {pair car,cdr;};
>> > pair_rep *rep;};                // Only needs to know sizeof(pointer).
>>
>> Class pair_rep has members of type pair, but type pair is not a
>> complete type at that point in the source code. The problem is not
>> the pointer to pair_rep, but the definition of pair_rep itself.
>>
>> Another example, without any cross-referencing:
>>
>> class A;
>>
>> class B {
>>     A a; // invalid, since A is not a complete type
>> };
>>
>>
>> It doesn't matter whether you use class B or not. Its definition
>> is invalid. The definition of class B must appear after that of
>> class A, or be removed entirely. In your example, you have to define
>> the nested class pair_rep after the definition of pair, not inside it.
>
>But what about the ANSI/ISO resolution, 9.7, page 421:
>
>"Like a member function, a nested class may be declared within the
>enclosing class and defined elsewhere in the same or containing scope,
>using the qualified name for the class definition:..."
>
>One might reasonably infer that 'like a member function' implied that the same
>sort of 'rewriting equivalence' exists for nested classes as well as member
>function bodies.

No, you cannot make that inference, and that is not what was intended
anyway. The difference is that in order to instantiate a class, you
do not need to know anything about the contents of its member functions.
You DO need to know everything about the types of its members, however.
Thus, members of a class may not have incomplete types.

---
Steve Clamage, stephen.clamage@eng.sun.com







Author: hbaker@netcom.com (Henry Baker)
Date: 1995/08/07
Raw View
In article <405boq$4br@engnews2.Eng.Sun.COM>, clamage@Eng.Sun.COM wrote:

> No, you cannot make that inference, and that is not what was intended
> anyway.

Good.  I'm glad that ANSI/ISO really cleared that one up.  From now on, whenever
I have a problem with interpreting the ARM, I'll consult my local mind-reader,
the Amazing Randi.  The next version of the ARM will be printed on tea leaf
paper, and the horoscopic signs of the ANSI/ISO committee members will
replace their email addresses.  I never realized that one of the phases
of the C++ compiler required sheep entrails to run.

--
www/ftp directory:
ftp://ftp.netcom.com/pub/hb/hbaker/home.html





Author: wil@ittpub.nl (Wil Evers)
Date: 1995/08/08
Raw View
In article <405boq$4br@engnews2.Eng.Sun.COM> clamage@Eng.Sun.COM (Steve
Clamage) writes:

[snip]

> The difference is that in order to instantiate a class, you
> do not need to know anything about the contents of its member functions.
> You DO need to know everything about the types of its members, however.
> Thus, members of a class may not have incomplete types.

IMHO this is one of the major drawbacks of the language as currently
defined. Having to textually list *all* the members of a class, including
the private ones, before the place an object of that class is instantiated
makes it hard to have a clean separation between interface and
implementation. We either have to live with the extra dependencies of the
user code on the private class members or declare a separate 'physical
representation' class whose instances are usually allocated on the heap,
which slows down the program.

For quite a few cases, it can be shown that a mechanism can be implemented
that would allow for the instantiation of objects whose full definition
has not yet been seen by the compiler, for instance because it is in a
different translation unit.

Has the committee considered any extensions to the current language
definition that would allow for such a mechanism? If so, why don't they
appear in the draft standard? If not so, why not?

- Wil





Author: clamage@Eng.Sun.COM (Steve Clamage)
Date: 1995/08/08
Raw View
In article 1427@ittpub, wil@ittpub.nl (Wil Evers) writes:
>
>IMHO this is one of the major drawbacks of the language as currently
>defined. Having to textually list *all* the members of a class, including
>the private ones, before the place an object of that class is instantiated
>makes it hard to have a clean separation between interface and
>implementation. We either have to live with the extra dependencies of the
>user code on the private class members or declare a separate 'physical
>representation' class whose instances are usually allocated on the heap,
>which slows down the program.

All true.

>For quite a few cases, it can be shown that a mechanism can be implemented
>that would allow for the instantiation of objects whose full definition
>has not yet been seen by the compiler, for instance because it is in a
>different translation unit.

In fact, other languages are defined that way, so this is not just a
theorectical observation. Ada, Turbo Pascal, and Modula-2 are examples.

>Has the committee considered any extensions to the current language
>definition that would allow for such a mechanism? If so, why don't they
>appear in the draft standard? If not so, why not?

The original intention of C++ was to use the C compilation model, and to
allow as much compatibility with C source and object code as possible.
This decision, IMHO, was responsible for the rapid growth and wide
popularity of C++. Many languages existed in 1985 and exist today which
were and are superior to C++ in many ways (or in every way), yet have only
a relatively small user community and few implementations.

Adding a module system to C++ would make it generally easier to use,
particularly for large projects, but would also make it a different language.
Stroustrup elected not to go that route in designing and developing C++, and
the C++ committee also elected not to go that route.

The committee felt it had more than enough work to do without adding
another huge pile of work. Figuring out a module system, or some subset
that would ease separation of interface and implementation, would take
years of experimentation in addition to all the work that has been done
and is still being done on the standard. For example, the template rules
were published in the ARM in 1990. The rules are still being worked on
as new problems with the definition of templates are discovered.

I'd like to suggest that someone take the time to define and implement
something like a module system for C++. To gain wide acceptance, it should
allow existing C++ source and object code to be integrated into Modular C++
(if I may coin the term) without modification, as well as provide the same C
compatibility as C++ currently has. When you are done, submit it for the next
round of C++ standardization. The timing should be about right.
--
Steve Clamage, stephen.clamage@eng.sun.com







Author: clamage@Eng.Sun.COM (Steve Clamage)
Date: 1995/08/04
Raw View
In article pci@galaxy.ucr.edu, thp@PROBLEM_WITH_INEWS_DOMAIN_FILE (Tom Payne) writes:
>
>I'm voting "overworked compiler writers."
>
>C++ does not use C's single-pass semantics for names occurring within
>a class.  Specifically, the C++ "reconsideration rule" states:
>
>   A name used in a class S must refer to the same declaration when
>   reevaluated in its context and in the completed scope of S, which
>   consists of the class S, S's base classes, and all classes enclosing
>   S.  [D&E, p.142]

The reconsideration rule requires at most two passes over a class, and a
second pass generally makes other things easier to deal with anyway.

Allowing use of an incomplete type, on the other hand, could require an
arbitrary number of passes. The original example required postponing
processing of a member type until the outer type was complete. This
postponement could be required in principle to any number of levels.

Instead of requiring this extra complexity in compilers, the language
rule requires you to use complete types where complete type data is
needed. The alternative would be to try to enumerate the cases where
an incomplete type is allowed and and where it is not. Such a rule would
be difficult to get right, difficult to understand and use. The current
rule is simple.

There is no difficulty in providing the definitions in the
correct order, and I would argue that the resulting code is easier
to read anyway. (You are entitled to a different opinion, of course.)
---
Steve Clamage, stephen.clamage@eng.sun.com







Author: hbaker@netcom.com (Henry Baker)
Date: 1995/08/05
Raw View
In article <3vthqo$m5t@engnews2.Eng.Sun.COM>, clamage@Eng.Sun.COM wrote:

> In article pci@galaxy.ucr.edu, thp@PROBLEM_WITH_INEWS_DOMAIN_FILE (Tom
Payne) writes:
> >
> >I'm voting "overworked compiler writers."
> >
> >C++ does not use C's single-pass semantics for names occurring within
> >a class.  Specifically, the C++ "reconsideration rule" states:
> >
> >   A name used in a class S must refer to the same declaration when
> >   reevaluated in its context and in the completed scope of S, which
> >   consists of the class S, S's base classes, and all classes enclosing
> >   S.  [D&E, p.142]
>
> The reconsideration rule requires at most two passes over a class, and a
> second pass generally makes other things easier to deal with anyway.

I don't fully understand 'reconsideration', but I don't think that it
is pertinent to this particular problem.

> Allowing use of an incomplete type, on the other hand, could require an
> arbitrary number of passes. The original example required postponing
> processing of a member type until the outer type was complete. This
> postponement could be required in principle to any number of levels.

The ARM says the following [9.2]:

"Members that are class objects must be objects of previously declared
classes.  In particular, a class cl may not _contain_ an object of class
cl, but it may _contain_ a _pointer_ or _reference_ to an object of class cl."

So, since the structure I was defining did not 'contain' a member of the
class being defined, but _does_ 'contain' a pointer to an object of the
class being defined, it meets all of the requirements of the ARM.  The
ARM wording makes no exception for lazy or unimaginative compiler
writers.  There is also no discussion whatsover about 'incomplete'
types, since all types are complete at the appropriate places.

> There is no difficulty in providing the definitions in the
> correct order, and I would argue that the resulting code is easier
> to read anyway. (You are entitled to a different opinion, of course.)

This is a little like asking the person providing information to a
sort routine to guarantee that the information is already in sorted
order, so that the sort routine only has to _check_ the order, but never
has to sort it.  I think that the whole _point_ of a compiler is to
handle the reorganization of information of this sort.

If I recall correctly, the program went something like this:

class pair
{class pair_rep {pair car,cdr;};
 pair_rep *rep;};                // Only needs to know sizeof(pointer).

As far as I can tell, all of the ARM requirements are met.

--
www/ftp directory:
ftp://ftp.netcom.com/pub/hb/hbaker/home.html





Author: clamage@Eng.Sun.COM (Steve Clamage)
Date: 1995/08/06
Raw View
hbaker@netcom.com (Henry Baker) writes:

>So, since the structure I was defining did not 'contain' a member of the
>class being defined,

Yes it did. The inner struct had a member of an incomplete type.

>class pair
>{class pair_rep {pair car,cdr;};
> pair_rep *rep;};                // Only needs to know sizeof(pointer).

Class pair_rep has members of type pair, but type pair is not a
complete type at that point in the source code. The problem is not
the pointer to pair_rep, but the definition of pair_rep itself.

Another example, without any cross-referencing:

class A;

class B {
    A a; // invalid, since A is not a complete type
};


It doesn't matter whether you use class B or not. Its definition
is invalid. The definition of class B must appear after that of
class A, or be removed entirely. In your example, you have to define
the nested class pair_rep after the definition of pair, not inside it.
--
Steve Clamage, stephen.clamage@eng.sun.com