[comp.lang.c++] goodbye cpp ???

jima@hplsla.HP.COM (Jim Adcock) (11/16/88)

As we head towards parameterized classes, isn't it time to
start thinking about getting rid of cpp, and its associated
non-syntactic hacks, and the compile/link inefficiencies
it introduces?

The big remaining issue I haven't seen addressed in C++
that would be necessary to get rid of cpp is finding
an equivalence to "#include an-import-definitions-file",
and "an-export-definitions-file.h"

Other language implementations I am familiar with address
this issue by adding an "import such-and-such-a-library"
declaration instead of the "#include such-and-such" and
adding an "export { such-and-such-definitions }" section
instead of using a .h file.  The "exported" definitions
get "compiled" and included in the .o file.

I'm NOT saying that putting the exported definitions in
the .o file is "the right way to go" -- for example alternatively 
a compiler could automatically generate a .h file and stick
it in an appropriate place as the mechanism for "exporting"
definitions.

[Clearly since the compiler now understands whether "library
 such-and-such" has been defined or not, multiple #include
 issues go away, as well as issues of how a particular human
 being should manually organize their .h files for maximal
 "efficiency."  These become issues for compiler writers
 to handle ONCE [per compiler] rather than compiler users
 having to address the issues ad-infinitem]

What I AM saying is: can't we get rid of cpp?  And incorporate
the remaining commonly used "features" of cpp in the language?
Even if one has an integrated program development environment,
wouldn't one still want a unit of program development work
analogous to the present "file" as a compilation unit, and
wouldn't one still want a hierarchical naming convention for
specifying what "libraries" one wants to build this unit
of program development effort [file] on top of?  Using 
"aClassName" to specify what "unit-of-development-effort"
[library] to build your development-effort on top of
doesn't seem like a good idea to me since everyone who
develops a library seems to believe their base class
is "the way to go" and therefor either names it "object"
or "Object."  So if one wants to use Joe's library and
Sue's library one needs to be able to differentiate
whose "object" class one wants to build a particular class 
on top of.  ???

Once we had these kind of features in the language, then
cpp could be a compiler option that one could either
turn on or off, and one wouldn't be forced to write
"hack" code, and better "linters", "pretty-printers",
etc, could be developed.....

Comments?

bright@Data-IO.COM (Walter Bright) (11/22/88)

In article <6590072@hplsla.HP.COM> jima@hplsla.HP.COM (Jim Adcock) writes:
<As we head towards parameterized classes, isn't it time to
<start thinking about getting rid of cpp
<The big remaining issue I haven't seen addressed in C++
<that would be necessary to get rid of cpp is finding
<an equivalence to "#include an-import-definitions-file",
<and "an-export-definitions-file.h"
<What I AM saying is: can't we get rid of cpp?

It's true that C++ seems to obviate the necessity of function-like
macros and macros for constants. However, one of the most *MAJOR* features
of the preprocessor is the ability to conditionally compile, i.e.
#if FEATURE
#else
#endif
This is a must for anyone who builds multiple executables from one source,
or who must cope with portability problems between systems. I don't see
any reasonable way this can be incorporated into the language itself.

agn@unh.cs.cmu.edu (Andreas Nowatzyk) (11/22/88)

Bjarne Strousrup's C++ book claims that inline functions
eliminate the need for most uses of macros and other
cpp-features. I find that statement overly optimistic and
plain false in many actual cases. Using the ATT version 1.2.1
of C++, I found inline functions to be inadequate because:

a) They produce poor code: Everything becomes one large
   comma-expression. Even optimized, the resulting assembly
   code is awful.

b) They lack control of temporary variables and/or objects.
   Numerous temporary variables are being added - frequently
   without need. Since my C-compiler doesn't optimize the use
   of registers, it is not possible to force inline functions
   to keep certain temporary variables in registers.

c) They have a lot of restrictions (inability to use functions
   that don't return values, no loops, troublesome nesting, etc.).

d) They lack control of instantiation: 2 (identical) 
   int inline function appear as the operands to an "==": the
   left inlines normally, the right is replaced by a function call!
   In this case, replacing an inline with an function call is
   fatal to the program (1), but the compiler doesn't even issue
   a warning.

e) No reasonable substitute for conditional compilation: in some
   modules certain functions have to be modified and parts of
   the function would not compile in that environment.

The bottom line is - for now - that inline-function are not even
close to replacing cpp. Note that performance is not the only reason:
lacking control on when and how to expand can result in incorrect code.

   --  Andreas

(1): In case you wondered how this can happen: the inline-function
     needs to know the line-number of where it appears in the source code.
     Is is part of an instrumentation package for parallel code running
     on a simulated multiprocessor.
-- 
   --  Andreas Nowatzyk  (DC5ZV)

   Carnegie-Mellon University	     Arpa-net:   agn@unh.cs.cmu.edu
   Computer Science Department         Usenet:   ...!seismo!unh.cs.cmu.edu!agn
-- 

mball@cod.NOSC.MIL (Michael S. Ball) (11/22/88)

In article <3637@pt.cs.cmu.edu> agn@unh.cs.cmu.edu (Andreas Nowatzyk) writes:
>Bjarne Strousrup's C++ book claims that inline functions
>eliminate the need for most uses of macros and other
>cpp-features. I find that statement overly optimistic and
>plain false in many actual cases. Using the ATT version 1.2.1
>of C++, I found inline functions to be inadequate because:
>
>a) They produce poor code: Everything becomes one large
>   comma-expression. Even optimized, the resulting assembly
>   code is awful.

You are confusing a language feature with an implementation.  If
a particular C compiler can't produce good code from comma expressions,
prehaps you should get a better one.  Better yet, look at some other
implementations of C++ and see what they do.  Cfront was the first
try at this, and is constrained by having to go through existing
C compilers.  There are limits to what you can do in that situation and
cfront does rather well.

>b) They lack control of temporary variables and/or objects.
>   Numerous temporary variables are being added - frequently
>   without need. Since my C-compiler doesn't optimize the use
>   of registers, it is not possible to force inline functions
>   to keep certain temporary variables in registers.

Try getting a better C compiler!

>c) They have a lot of restrictions (inability to use functions
>   that don't return values, no loops, troublesome nesting, etc.).

Huh!  I don't know what you mean by "troublesome nesting", but you can
certainly have inline functions without return values.  The decision not to
include looping constructs is reasonable, particularly for a compiler which
must produce C.  The contortions needed to include loops in a value
context give more overhead than a function call.  Also, if you have a loop,
the function call overhead is most likely a small percentage of running
time.  Yes, I know that sometimes the loop isn't executed very often, I'm
talking about the likely payback for the effort.

>d) They lack control of instantiation: 2 (identical) 
>   int inline function appear as the operands to an "==": the
>   left inlines normally, the right is replaced by a function call!

This is an implementation quirk of cfront.

>   In this case, replacing an inline with an function call is
>   fatal to the program (1), but the compiler doesn't even issue
>   a warning.

If replacing an inline function with a call is fatal you have a
programming error.  The "inline" declaration is simply a hint to
the compiler.  I see no need for a warning.  There are many situations
in which an inline function has to be instantiated.  For example,
when it is virtual and a pointer to it is needed for a virtual table.

>e) No reasonable substitute for conditional compilation: in some
>   modules certain functions have to be modified and parts of
>   the function would not compile in that environment.

But we were talking about replacing macros, not conditional compilation.

>The bottom line is - for now - that inline-function are not even
>close to replacing cpp. Note that performance is not the only reason:
>lacking control on when and how to expand can result in incorrect code.

Untrue.  If control of expansion matters the code was already incorrect.

>(1): In case you wondered how this can happen: the inline-function
>     needs to know the line-number of where it appears in the source code.
>     Is is part of an instrumentation package for parallel code running
>     on a simulated multiprocessor.

The only way an inline function can tell the line number at a call
is to pass the line number as a parameter to the function, in which
case it will work whether expanded or not.  Inline function processing is
done long after preprocessing, which is where the line number is known.

Michael S. Ball
TauMetric Corporation
1094 Cudahy Pl. Ste 302
San Diego, CA 92110
(619)275-6381

crm@romeo.cs.duke.edu (Charlie Martin) (11/22/88)

 I want to throw in one quibble into the discussion of eliminating cpp.

 Several people have suggested that eliminating cpp would mean giving
 up conditional compilation, and I want to point out that this just
 isn't so.  There is no reason why the scanner can't handle conditional
 compilation as well, just as comments are handled.  Nested
 conditionals means putting a little more smarts into the scanner, but
 the loss in performance in the scanner is almost certainly less than
 the loss of having a whole nother compilation phase.  AND it means
 there doesn't need to be two phases for scanning, two parsers, two
 symbol tables, two "code generators" etc etc.

 The VMS C compiler (for all its other problems) does this, I believe.
Charlie Martin (crm@cs.duke.edu,mcnc!duke!crm) 

agn@unh.cs.cmu.edu (Andreas Nowatzyk) (11/23/88)

In article <2191@pt.cs.cmu.edu> mball@cod.NOSC.MIL (Michael S. Ball) writes:
> ... If
> a particular C compiler can't produce good code from comma expressions,
> prehaps you should get a better one.  Better yet, look at some other
> implementations of C++ and see what they do. ...
>
Sure: get a better compiler is the universal answer. Unfortunately that
isn't a viable option in many cases (like in this one).
The point is: a macro gives you more controll over what is going on,
inline functions don't and there are cases where you need precise control.

> Huh!  I don't know what you mean by "troublesome nesting", but you can
> certainly have inline functions without return values.  The decision not to
Inline functions cannot use functions that have no return-value (a
side-effect of using comma-expressions). Inline functions calling other
inline function sometimes inline, somethimes thay don't. Exactly when
each case happens is rather arbitrary.


>The only way an inline function can tell the line number at a call
>is to pass the line number as a parameter to the function, in which
>case it will work whether expanded or not.  Inline function processing is
>done long after preprocessing, which is where the line number is known.
Not so fast: the problem is instrument a piece of code whitout changeing
it. Whenever that application code is dealing with the simulated environment,
the simulator need to know about it. It is possible to do that in C++,
but the details are quite messy and involve operator overloading, the
use of non-trivial macros and some other stuff.

  --  Andreas
-- 
   --  Andreas Nowatzyk  (DC5ZV)

   Carnegie-Mellon University	     Arpa-net:   agn@unh.cs.cmu.edu
   Computer Science Department         Usenet:   ...!seismo!unh.cs.cmu.edu!agn
-- 

henry@utzoo.uucp (Henry Spencer) (11/24/88)

In article <12903@duke.cs.duke.edu> crm@romeo.cs.duke.edu (Charlie Martin) writes:
> ... There is no reason why the scanner can't handle conditional
> compilation as well, just as comments are handled...
> the loss in performance in the scanner is almost certainly less than
> the loss of having a whole nother compilation phase...

You are confusing design with implementation.  Many C compilers implement
the whole preprocessor as part of their scanner.  The separation into a
distinct phase is basically a historical artifact in AT&T compilers.
-- 
Sendmail is a bug,             |     Henry Spencer at U of Toronto Zoology
not a feature.                 | uunet!attcan!utzoo!henry henry@zoo.toronto.edu

orr@cs.glasgow.ac.uk (Fraser Orr) (11/24/88)

In article <3637@pt.cs.cmu.edu> agn@unh.cs.cmu.edu (Andreas Nowatzyk) writes:
>Bjarne Strousrup's C++ book claims that inline functions
>eliminate the need for most uses of macros and other
>cpp-features. I find that statement overly optimistic and
>plain false in many actual cases. Using the ATT version 1.2.1
>of C++, I found inline functions to be inadequate because:
>
Summary of critisims -
 a) Produce poor code
 b) Produce large numbers of temporary variables
 c) They have a lot of restrictions (loops etc)
 d) Lack control of instansiation
 e) There is no reasonalble substitute for conditional compilation.

Firstly, it seems points a) and b) are a fair critisism of
the compilers that we have at present. I would just say,
write a better compiler.

Restrictions - There are some unfortunate restrictions,
and there is no easy solution to this. But then again,
I never could write recursive macros anyway so its no
great loss to me.

Lack of control of instansiation, I don't see where the problem could
be.  The whole point of inline functions is that they should be
semantically identical to a function call.  I'd be interested to see an
example of this problem.

There is a reasonable substitute for conditional compilation,
its called modularisation. You write a system dependent file
for each system, and then link the appropriate one to a file
called "sys_depends.h". Simple, eh? What about if I want
one of my functions to do this for system A and that for system
B, you may ask. Simple, you write two functions, one for each system.
This might require you to have something like

SYS5= ...
BSD= ...
VMS= ...
SYSDEPENDS = SYS5

in your makefile, and you have to edit a line of it - big deal!
In fact with all these configure programs that seem to be distributed
with sources these days, you probably wouldn't even have to do that.

>   --  Andreas Nowatzyk  (DC5ZV)

==Fraser Orr ( Dept C.S., Univ. Glasgow, Glasgow, G12 8QQ, UK)
UseNet: {uk}!cs.glasgow.ac.uk!orr       JANET: orr@uk.ac.glasgow.cs
ARPANet(preferred xAtlantic): orr%cs.glasgow.ac.uk@nss.cs.ucl.ac.uk

bright@Data-IO.COM (Walter Bright) (11/24/88)

In article <12903@duke.cs.duke.edu> crm@romeo.cs.duke.edu (Charlie Martin) writes:
< Several people have suggested that eliminating cpp would mean giving
< up conditional compilation, and I want to point out that this just
< isn't so.  There is no reason why the scanner can't handle conditional
< compilation as well, just as comments are handled.
< AND it means
< there doesn't need to be two phases for scanning, two parsers, two
< symbol tables, two "code generators" etc etc.

When I protested the loss of conditional compilation by dumping cpp, I
protested the loss of the *functionality* of cpp, not its implementation
as a separate pass. In my compiler (Zortech), the preprocessor is integrated
into the scanner, there is no separate pass. The compiler is obviously
much faster without the extra pass.

mball@cod.NOSC.MIL (Michael S. Ball) (11/24/88)

In article <3663@pt.cs.cmu.edu> agn@unh.cs.cmu.edu (Andreas Nowatzyk) writes:
>
>In article <2191@pt.cs.cmu.edu> mball@cod.NOSC.MIL (Michael S. Ball) writes:
>> ... If
>> a particular C compiler can't produce good code from comma expressions,
>>
>Sure: get a better compiler is the universal answer. Unfortunately that
>isn't a viable option in many cases (like in this one).
>The point is: a macro gives you more controll over what is going on,
>inline functions don't and there are cases where you need precise control.

When we talk about eliminating macros we are talking about LANGUAGE, not
some deficient implementation which you (and far too many others) are
required to use.  Sure, some compilers may be so bad you need "asm"
inserts to replace arithmetic expressions, but that doesn't mean that
there are flaws in idea of expressions.  If you need that precise "control"
use macros, but don't be surprised when the compiler generates bad code
for them too.

>> Huh!  I don't know what you mean by "troublesome nesting", but you can
>> certainly have inline functions without return values.  The decision not to
>Inline functions cannot use functions that have no return-value (a
>side-effect of using comma-expressions).

This is absolutely incorrect.  We do it all the time and they work just fine.
I suspect you are seeing some other effect and confusing causes.

>Inline functions calling other
>inline function sometimes inline, somethimes thay don't. Exactly when
>each case happens is rather arbitrary.

Not arbitrary, just unknown to you, and possibly different in different
implementation.  It is a decision the compiler makes, not the programmer,
and can have no effect on the semantics.  Our own compiler has no such
limits at all.  I don't know the details of the cfront algorithm, but
the only time I have seen it happen for recursive functions, where
the expansion must stop somewhere.

>>The only way an inline function can tell the line number at a call
>>is to pass the line number as a parameter to the function, in which
>>case it will work whether expanded or not.  Inline function processing is
>>done long after preprocessing, which is where the line number is known.
>Not so fast: the problem is instrument a piece of code whitout changeing
>it. Whenever that application code is dealing with the simulated environment,
>the simulator need to know about it. It is possible to do that in C++,
>but the details are quite messy and involve operator overloading, the
>use of non-trivial macros and some other stuff.

This does not change the point, which is that inline function expansion/
non-expansion cannot change the semantics of the call.  It is not affected
by the environment of the calling location except for values passed as
parameters.  Your statement about requiring expansion to work properly 
simply demonstrates a lack of understanding of the definition of inline
functions, which are NOT in any sense macros.

Michael S. Ball
TauMetric Corporation

pcg@aber-cs.UUCP (Piercarlo Grandi) (11/24/88)

In article <12903@duke.cs.duke.edu> crm@romeo.cs.duke.edu (Charlie Martin) writes:
    
     I want to throw in one quibble into the discussion of eliminating
     cpp.

     Several people have suggested that eliminating cpp would mean
     giving up conditional compilation, and I want to point out that
     this just isn't so.  There is no reason why the scanner can't
     handle conditional compilation as well, just as comments are
     handled.

Yes, but there is an important area where a macroprocessor is
essential, and is in defining new syntax.

I have a custom, when defining a new data structure such as a list, to
provide a for-like new statement that scans it; I have an habit to
define other innocent syntax extensions, such as new constructs for
recursive descent parsing, or for state machines. If lightly used, and
discreet, syntax extensions can be extremely helpful in making the
program more readable.

There is a big danger here that syntax extensions be used to make
programs even more unreadable and unfamiliar; most people agree that
the original Bourne shell source, where Steven Bourne tried to recreate
the looks of his beloved Algol68C, was not a good idea. I believe that
they have a role when they are used to abstract from the gritty detail,
without trying to later dunamentally the nature of the language they
apply to.

On the other hand I do really believe that cpp in C++ is not that
useful; #include's should be done with import/export, for example, and
definitions can be done with inlines and consts, and conditional
compilation can be done most often by a suitable constant expression in
a if; as to syntax extensions, I think something like ML/1 would be the
ideal thing here.
-- 
Piercarlo "Peter" Grandi			INET: pcg@cs.aber.ac.uk
Sw.Eng. Group, Dept. of Computer Science	UUCP: ...!mcvax!ukc!aber-cs!pcg
UCW, Penglais, Aberystwyth, WALES SY23 3BZ (UK)

agn@unh.cs.cmu.edu (Andreas Nowatzyk) (11/26/88)

>In article <2214@pt.cs.cmu.edu> mball@cod.NOSC.MIL (Michael S. Ball) writes:
> [regarding inlines with non-value returning function calls]
> This is absolutely incorrect.  We do it all the time and they work just fine.
> I suspect you are seeing some other effect and confusing causes.

It would help if you would read the article you are responding to. My
comments concerned a specific C++ compiler (ATT V1.2.1) and are quite
correct:

"foo.c", line 15: sorry, not implemented: non-value-returning inline bar() \
called in value-returning inline xyz::get()

No big deal really: just return some dummy and cfront is happy.

> [With regard to compiler decisions on when to inline]
> Not arbitrary, just unknown to you, and possibly different in different
> implementation.  It is a decision the compiler makes, not the programmer,
> and can have no effect on the semantics.  Our own compiler has no such

I guess that is normally correct, but I had to resort to tricks to
get beyond some C++ language limitations. In the case at hand, a filter is
inserted between cfront and the C-compiler. This filter substitutes certain
code fragments in the inlined functions. One of the tasks is to insert
said line numbers, which are derived from the "#line" directives that CC
provides if run with the "-Fc" switch. Naturally inlining vs. function
calling changes the semantic dramatically. Not-inlining is an error here. 

Perhaps there are better ways to do this. How about a constructive
suggestion Mr. Ball? The problem is a simulator for a parallel architecture
that must be able to run of-the-shelf benchmarks. You are not allowed to
change these source codes. The result of a simulator run is an annotated
source code listing that states for each line how often it is executed, how
much CPU and how much memory-access time it took. To complicate matters a
bit, a few hundered copies are run concurrently and the entire process has
to complete in less than a CPU-day (a non-trivial contraint!).  CPU times
are computed from analyzing the assembly-output of the C-compiler and are
available to the C++ code through a filter operating on the assembly files
that leaves the number of clock cycles in certain places (results are
deterministic and don't depend on some hardware timer and/or system call).

I think this task could not be done with a smart all-in-one compiler:
is was absolutely necessary to add filters (small yacc/lex programs)
between cpp/cfront/cc/as.

> Your statement about requiring expansion to work properly 
> simply demonstrates a lack of understanding of the definition of inline
> functions, which are NOT in any sense macros.

The point of this discussion is that I assert that macros are an indispensible
part of C++ and that inline functions - contrary to some claims - cannot
replace them. One important reason is that the programmer has little to no 
control over the expansion of inlines.

-- 
   --  Andreas Nowatzyk  (DC5ZV)

   Carnegie-Mellon University	     Arpa-net:   agn@unh.cs.cmu.edu
   Computer Science Department         Usenet:   ...!seismo!unh.cs.cmu.edu!agn
-- 

henry@utzoo.uucp (Henry Spencer) (11/27/88)

In article <265@aber-cs.UUCP> pcg@cs.aber.ac.uk (Piercarlo Grandi) writes:
>On the other hand I do really believe that cpp in C++ is not that
>useful; #include's should be done with import/export...

In other words, with new constructs that will have to resemble #include
in many ways, except that they will undoubtedly be more complex.  Why bother?
Is there some specific benefit, apart from the change in syntax?  Please
remember that the new scheme will have to be one that can cope with search
paths, separations between projects, standard/optional/local/project/program
libraries, debugging vs compile-for-production, etc. -- simply having the
construct always look in one standard place for things to import is not
nearly good enough.  Only toy compilers always use the same libraries for
all compilations.

>...definitions can be done with inlines and consts...

Not all definitions, although to some extent it depends on where you draw
the line between "definitions" and "syntax extensions".

> ...conditional compilation can be done most often by a suitable constant
>expression in a if...

How do you make this work for declarations?  (I made the same blunder long
ago.)

>as to syntax extensions, I think something like ML/1 would be the
>ideal thing here.

"Ideal" in what sense?  Certainly not from an implementor's point of view.
Or from the viewpoint of a user who has to understand already-written code.

The preprocessor may be ugly, but at least it is (apart from some of the
fine points of macro expansion in draft-ANSI C) relatively simple and easy
to understand.  (Programs that exploit it in fancy ways can be cryptic, but
programs that exploit more complex mechanisms in fancy ways will be worse.)
That is not a trivial virtue.
-- 
Sendmail is a bug,             |     Henry Spencer at U of Toronto Zoology
not a feature.                 | uunet!attcan!utzoo!henry henry@zoo.toronto.edu

pcg@aber-cs.UUCP (Piercarlo Grandi) (11/28/88)

In article <1988Nov27.011355.29290@utzoo.uucp> henry@utzoo.uucp (Henry Spencer) writes:

    In article <265@aber-cs.UUCP> pcg@cs.aber.ac.uk (Piercarlo Grandi) writes:
    >On the other hand I do really believe that cpp in C++ is not that
    >useful; #include's should be done with import/export...
    
    In other words, with new constructs that will have to resemble
    #include in many ways, except that they will undoubtedly be more
    complex.  Why bother?  Is there some specific benefit, apart from
    the change in syntax?

There are two points; one is speed, and the other is easier
configurability.  I have seen many export/import syntaxes for various
languages, and they do not seem to be more complex than #include.  I
would have doubts... Also, I do not like the wholesale imports favoured
by #include.

    Please remember that the new scheme will have to be one that can
    cope with search paths, separations between projects,
    standard/optional/local/project/program libraries, debugging vs
    compile-for-production, etc. -- simply having the construct always
    look in one standard place for things to import is not nearly good
    enough.  Only toy compilers always use the same libraries for all
    compilations.

I never advocated toy compilers! Indeed I think that export/import
could help a lot with the problems you mention, more so than cpp.
Well, after all #include is not especially user frindly or powerful. I
think that a proper export/import facility, with multics like paths,
would be more efficient, have finer resolution, be much more flexible
and easier to use than the often hairy things you have to do with
#includes and coordinate them with the linker etc... by hand.

    > ...conditional compilation can be done most often by a suitable constant
					     ^^^^^^^^^^
    >expression in a if...
    
    How do you make this work for declarations?  (I made the same blunder long
    ago.)

Most often, most often. I understand that C++ does not yet have fully
"executable" declarations :-). Another obvious example that cannot be done
but by macroprocessor is *parts* of statements or other syntactical units.
In other words, all cases where you "temporarily" violate the syntax
of the underlying language.
    
    >as to syntax extensions, I think something like ML/1 would be the
    >ideal thing here.
    
    "Ideal" in what sense?  Certainly not from an implementor's point of view.

Ideal because it allows the definitions of more humane-looking macro patterns
(not all syntax extensions you may want look like functions calls), it is
quite fast, the implementation is there. 
    
    Or from the viewpoint of a user who has to understand already-written code.

This I do not understand.

    The preprocessor may be ugly, but at least it is (apart from some of the
    fine points of macro expansion in draft-ANSI C) relatively simple and easy
    to understand.  (Programs that exploit it in fancy ways can be cryptic, but
    programs that exploit more complex mechanisms in fancy ways will be worse.)
    That is not a trivial virtue.

Well, it is quite fast, and it is quite ugly. Relatively easy and
simple to understand, well, you know, most people that use it use it
only for the simplest things, and still they are often astonished (how
many do not fully parenthesize EVERYTHING ?). Rather than simple, I'd
say simplistic -- as soon as you have to do non trivial things you have
to be a real wizard. It is a matter of opinion whether other mechanisms
would be rather less or more hairy in complex situations than cpp.

I would like to note that this discussion started with people being not
very satisfied with the preprocessor. It was a good thing (can we say a
very good hack?) when it was born, but fortunately the tendence now is
to bring more of its functions in the language framework, so that is be
more consistent and efficient.  This is indeed the spirit of C++, as
Dr.  Stroustrup correctly remarks.

On the other hand I think that having a macro processor is ESSENTIAL in
any case, because certain things can be done only outside the framework
of a language, i.e. things that violate its syntax, and require pure
macro processing/unrestricted string manipulation, such as conditional
compilation of declarations, or of parts of statements. I'd rather have
a nicer macroprocessor than cpp for this purpose, though.

As to practicalities, the here and now, cpp is with us, and for a long
time.  If you want to write portable code, put up with cpp. Don't even
try to modify it or to extend it. I would not even use a #pragma idempotent,
which is a good example of a useful, nearly harmless extension as you
can find -- a lot of cpp/compiler pairs or unities do not support #pragma yet.
-- 
Piercarlo "Peter" Grandi			INET: pcg@cs.aber.ac.uk
Sw.Eng. Group, Dept. of Computer Science	UUCP: ...!mcvax!ukc!aber-cs!pcg
UCW, Penglais, Aberystwyth, WALES SY23 3BZ (UK)

db@lfcs.ed.ac.uk (Dave Berry) (11/29/88)

In article <6590072@hplsla.HP.COM> jima@hplsla.HP.COM (Jim Adcock) writes:
>As we head towards parameterized classes, isn't it time to
>start thinking about getting rid of cpp, and its associated
>non-syntactic hacks?

If what you want is to get rid of such "abuses" as

#define BEGIN {
#define Case break;  case
#define K *1024

a better way to do it would be to restrict the rhs of #defines in the
C++PP to be complete expressions or declarations.  Clearly the C++PP
would have to diverge from the CPP to do this, and equally clearly there
would have to be a compiler option to use the CPP instead, to retain
compatibility with C.

I put "abuses" in quotes as some people like the examples shown and some
dislike them.


If you still want to get rid of the preprocessor, one approach would be to
persuade people to support each of the language extensions that would be
needed, in turn, until people choose to stop using the preprocessor for
anything (except conditional compilation?).  I don't think you're likely to
gain much support for an attempt to remove it by fiat; to do so you'd at least
need to be sure you hadn't missed any desirable functionality.


The current definition of C++ can't do half the things that the CPP can.
For example, inline functions can't replace macros that have declarations
or constant expressions on their rhs.  An example of the latter is:

#define CTRL(x)  (x - 0100)
...
   case CTRL('A'):

Not that I'd mind a bit more restriction on what the preprocessor could
do -- just as long as I can still do all the things *I* want (semi ;-).

BTW, I like the idea of a once-only include.

Dave Berry,	Laboratory for Foundations of Computer Science, Edinburgh.
		db%lfcs.ed.ac.uk@nss.cs.ucl.ac.uk
		<Atlantic Ocean>!mcvax!ukc!lfcs!db

jima@hplsla.HP.COM (Jim Adcock) (11/29/88)

My apologies to those people who read my original "catchy title"
[goodbye cpp] and turned on the flames without reading what
I was proposing.  In summary, what I was proposing:

1) Get parameterized classes into C++ compilers, so that people don't
   have to use cpp to implement ultra-kludge hack "cpp-parameterized"
   classes.

2) Add some kind of import/export capability to the language so 
   that one isn't forced to play a bunch of hack games with .h
   files, multiple #includes, to "optimize" the compilation process,
   where these would be simple for the compiler to handle if there
   were import/export capabilities in the language.  My claim is
   that one would still want some kind of import/export capabilities
   even if one had an "integrated programming environment" ala Smalltalk.

3) Make sure in-line functions work in C++ compilers and are in fact
   in-lined where to do so would represent a "reasonable" optimization.

4) Make sure that C, C++ compilers have enough "optimization-sense"
   that they do not generate obviously dead code:

   if (0)
   {
       /* lines of code */
   }

shouldn't generate any more code in a C or C++ compiler in 1988 than:

#if 0
       /* lines of code */
#endif

[If your C or C++ compiler in 1988 isn't at least as smart as cpp
 in performing these kinds of "optimizations" -- you've got problems! :-]

5) Allow continued access to cpp for those people who need it for
   backwards compatibility, or because they have some "valid" reason
   to hack source code.  [Just don't do anything that forces the
   rest of us to use it.]  Cpp could be accessed via a compile-time
   option, for example.  Or you could contiue to allow use of cpp
   be default, and give the rest of us a compile time option to
   turn off recognition of cpp constructs.

6) Recognize cpp as not being part of the C++ language, but rather
   is a text preprocessor, which may or may not be integrated into
   the first pass of a C++ compiler.  Use of cpp may be necessary
   occasionally, as assembly is necessary occasionally, but 
   continual use of cpp in every day tasks -- such as specifying what
   classes and libraries a new class is to be built upon, demonstrates
   features missing from the C++ language.

7) Recognize the C++ language is distinct and different from [but
   related to] any particular implementation of C++ on any operating
   system, using any C++ compiler, or combination of "translator"
   and C compiler.

jima@hplsla.HP.COM (Jim Adcock) (11/30/88)

> In other words, with new constructs that will have to resemble #include
> in many ways, except that they will undoubtedly be more complex.  Why bother?

Well, for one thing, people are publishing long articles on notes on
how to fine tune (by hand) .h files so that you can compile c++ programs in a 
reasonably finite amount of time.  Why not let the compiler figure out these
"optimizations" for the user?  Also if .h files were kept around in "compiled"
form, there could be time savings over recompiling this code whenever the
class is used by another class.

Also, the .h separation from the .cc file [or whatever extension you use]
is just not a natural way to program -- far better to have your class
definitions up front in an export section, followed by your functions.

As long as there is .h files you cannot do true data hiding of the 
implementation.

Presently compilers have no way of knowing what is really export material
verses import material, so that multiple vtables get generated.  Of course
you can prevent this with a compile time option -- another hack.

Finally, is not specification of which classes you really want to build
your classes on top of really part of the language?  If so, why not
recognize this, why force a hack into every .cc file, every class that
gets written?

art@felix.UUCP (Art Dederick) (12/01/88)

Conditional compilation without cpp:

const UNIX = TRUE;
const MSDOS = FALSE;

if (UNIX) {
	/* code for UNIX systems only */
}
else if (MSDOS) {
	/* code for MSDOS systems only */
}

The compiler could dump the code for the constant conditional
that evaluates FALSE and keep the code that evaluates TRUE.
This is the simplest of optimizations and any compiler that does not
do this should be shot (at least the compiler writer should be :-).

Art D.

guy@auspex.UUCP (Guy Harris) (12/01/88)

>Conditional compilation without cpp:

As has been pointed out before:

	const UNIX = TRUE;
	const MSDOS = FALSE;

	if (UNIX) {
		foobar()
		{
			/* code sub one */
		}
	}
	else if (MSDOS) {
		bletch()
		{
			/* code sub two */
		}
	}

isn't going to work very well (I'm sure other examples can be
constructed, if you consider this one unlikely)....

bright@Data-IO.COM (Walter Bright) (12/03/88)

In article <72227@felix.UUCP> art@felix.UUCP (Art Dederick) writes:
<Conditional compilation without cpp:
<const UNIX = TRUE;
<const MSDOS = FALSE;
<if (UNIX)
<	/* code for UNIX systems only */
<else if (MSDOS)
<	/* code for MSDOS systems only */

You're right, and most compilers do this. But you miss the point. Not all
porting problems can be solved this simply. As a trivial example:

#if COMPILER_A
	typedef long double abc;
#elif COMPILER_B
	typedef double abc;
#else
	}}}}	/* cause a syntax error */
#endif

Basically, there's a lot more differences between compilers than can
be handled with an if(0)...else... construct.

jima@hplsla.HP.COM (Jim Adcock) (12/06/88)

> <Conditional compilation without cpp:
> <const UNIX = TRUE;
> <const MSDOS = FALSE;
> <if (UNIX)
> <	/* code for UNIX systems only */
> <else if (MSDOS)
> <	/* code for MSDOS systems only */
> 
> You're right, and most compilers do this. But you miss the point. Not all
> porting problems can be solved this simply. As a trivial example:
> 
> #if COMPILER_A
> 	typedef long double abc;
> #elif COMPILER_B
> 	typedef double abc;
> #else
> 	}}}}	/* cause a syntax error */
> #endif
> 
> Basically, there's a lot more differences between compilers than can
> be handled with an if(0)...else... construct.

Maybe, but let me ask a devil's advocate type question:

WHY NOT allow conditional compilation of typedefs?  What would it take
to allow the language to do this?

[suffice to say, I'm assuming that the conditional expressions must 
 eval to compile time constants.]

If the cpp "hack" above is necessary to write portable programs,
as indeed it seems to be, then why not admit this is a capability
required in everyday programming practice -- and make it part of
the language?

I do not doubt there is always going to be weird stuff that is best
handled by cpp, or some more qualified preprocessor, but shouldn't
"everyday" programming needs be built into a language?  As opposed
to forcing you to "fake" a capability by using a preprocessor?

Wouldn't, for instance:

class intObject
{
    int anInt;

    if (TEST | DEBUG)
    {
        char * name;
        ...
    }
    ...
}


-- wouldn't this be a reasonable way to be able to specify variables
and functionality that are only needed for testing and debugging a 
class, that you don't want to provide in the finalized software?
        
And if you start hacking cpp to force users of cpp to follow the 
syntax of the language -- aren't you really admitting that that
capability would best be built into the language in the first place?    

guy@auspex.UUCP (Guy Harris) (12/06/88)

>If the cpp "hack" above is necessary to write portable programs,
>as indeed it seems to be, then why not admit this is a capability
>required in everyday programming practice -- and make it part of
>the language?

Umm, err, at least in C, things like #ifdef *are* part of the language;
see recent draft ANSI C specs, for instance.  The "Reference Manual"
section of the "The C++ Programming Language" also lists them, so I
guess I'd consider them to be part of C++ as well. 

>I do not doubt there is always going to be weird stuff that is best
>handled by cpp, or some more qualified preprocessor, but shouldn't
>"everyday" programming needs be built into a language?  As opposed
>to forcing you to "fake" a capability by using a preprocessor?

Again, the fact that some implementations of C or C++ happen to
implement those constructs by running source code through a preprocessor
and then through what some people might prefer to think of as the "real"
compiler doesn't mean they have to be implemented that way, or that
they're not part of the language.

There is some precedent, if I remember correctly, for language features
that can be considered to affect the source code seen by the "real"
compiler.  I think PL/I provide (more-or-less) equivalent capabilities,
although the syntax used more closely resembled that of PL/I than the
syntax of the "#" functions resembles that of C.  (I seem to remember
PL/I using "%" as the magic character to distinguish "pre-processor"
constructs from "real" language constructs.)

wgh@ubbpc.UUCP (William G. Hutchison) (12/06/88)

In article <72227@felix.UUCP>, art@felix.UUCP (Art Dederick) writes:
> Conditional compilation without cpp:
> 
> const UNIX = TRUE; const MSDOS = FALSE;
> 
> if (UNIX) { 		/* code for UNIX systems only */
> } else if (MSDOS) {  	/* code for MSDOS systems only */
> }
> The compiler could dump the code for the constant conditional ... FALSE ...

No, this does not solve the general case.
What if "/* code for UNIX systems only */" includes function definitions?
C and C++ are not fully nested like Algol, Pascal, etc.:
function definitions may not occur within blocks.

So your proposal loses some important semantics of #ifdef ... #endif.
-- 
Bill Hutchison, DP Consultant	rutgers!liberty!burdvax!ubbpc!wgh
Unisys UNIX Portation Center	"What one fool can do, another can!"
P.O. Box 500, M.S. B121		Ancient Simian Proverb, quoted by
Blue Bell, PA 19424		Sylvanus P. Thompson, in _Calculus Made Easy_

ok@quintus.uucp (Richard A. O'Keefe) (12/08/88)

In article <588@auspex.UUCP> guy@auspex.UUCP (Guy Harris) writes:
>There is some precedent, if I remember correctly, for language features
>that can be considered to affect the source code seen by the "real"
>compiler.  I think PL/I provide (more-or-less) equivalent capabilities,

PL/I "compile-time statements" (the facility was commonly called the
preprocessor...).  E.g.
	% DECLARE EXPR CHARACTER;
	% EXPR = '(FOO*BAZ)';
is like #define EXPR (FOO*BAZ)

	% FN: PROCEDURE (X, Y) RETURNS CHARACTER;
	    DECLARE (X, Y) CHARACTER;
	    RETURN ('F(' || X || ',' || Y || ',' || X || ')');
	% END FN;

is like	#define FN(X,Y) F(X,Y,X).  Compile-time variables don't have to
act as macros.  There are compile-time %IF and %DO statements, even
labels and % GOTO.

Burroughs Extended Algol had a similar feature, using apostrophe as the
prefix for compile-time statements.  This was in addition to $INCLUDE
and DEFINE.  Preprocessor variables could be arrays.

The language SAIL also provides similar features, described in TOPLAS
some years ago.

The interesting thing about these three is that to the best of my
knowledge all three were implemented using coroutines.  

lpringle@bbn.com (Lewis G. Pringle) (12/14/88)

It is not quite true that Inlines preserve the semantics of function calls.
At least - in cfront 1.2.  Here is an example.

inline Catch()
{
	return (setjmp (failureHandlers[which++]));   // sb !setjmp???
}

Fred ()
{
	if (Catch()) {
		DoSomeStuffThatMayCauseFailure ();
	}
	else {
		CleanUp ();
	}
}

Here it is really rather important that the inline act as a macro.
I may be an unintended feature of the implementation that the above
code works.  It certainly violates the 'preserve the semantics' criterion
bandied about lately.  Yet I contest that it is useful to have inlines
work the way they do - especially if you are thinking of trashing cpp!

					Lewis.
"OS/2: half an operating system for half a computer."

In Real Life:		Lewis Gordon Pringle Jr.
Electronic Mail:	lpringle@labs-n.bbn.com
Phone:			(617) 873-4433

jss@hector.UUCP (Jerry Schwarz) (12/15/88)

In article <33500@bbn.COM> lpringle@labs-n.bbn.com (Lewis G. Pringle) writes:
>
>
>It is not quite true that Inlines preserve the semantics of function calls.
>At least - in cfront 1.2.  Here is an example.
>
>inline Catch()
>{
>	return (setjmp (failureHandlers[which++]));   // sb !setjmp???
>}

This code has undefined semantics.  Once you return from Catch. the
effect of jumping to the saved buffer is undefined.  It may happen to
have the desired effect in one compilation system becasue the inline
causes "macro-like" expansion, but this does not mean that it will
have the desired effect in all C++ compilation systems.

"Preserving semantics" means that the code generated by inlined
functions must conform to C++ semantics for function calls.   It does
not mean that it must have the identical effect to what would be
generated in the absense of inline. For example, the order of
evaluation of function arguments is not specificed in C++.
Declaring a function inline might cause that order to be different.

Also note, that setjmp/longjmp must be used very carefully (if at
all) in C++ programs because destructors are not called when longjmp
unwinds the stack.


Jerry Schwarz
AT&T Bell Labs, Murray Hill

lpringle@bbn.com (Lewis G. Pringle) (12/15/88)

>The current definition of C++ can't do half the things that the CPP can.
>For example, inline functions can't replace macros that have declarations
>or constant expressions on their rhs.  An example of the latter is:
>
>#define CTRL(x)  (x - 0100)
>...
>   case CTRL('A'):
>
>Not that I'd mind a bit more restriction on what the preprocessor could
>do -- just as long as I can still do all the things *I* want (semi ;-).

This example reminds me of a suggestion for an extention to C++.  The problem
with the above code is that switch statements require the cases to be integer
constants - though there is really no good reason for this.

All too often I have seen code like
	if (a == thing1) {
	}
	else if (a == thing2) {
	}
	...
where either a is not an inteer, or the things are not constant.  Sometimes
the == is replaced by a strcmp().

When c++ sees a switch statement, it could generate the skip-chain
of if-then-elseifs from that, so long as the argument to the switch
and the cases where all =='able.  (As an optimization - of course -
when the cases are all constant it could just output the C switch statement
as-is.)

For example, the cfront could translate
	switch (GetSomeStringFromSomeWhere) {
		case "Fred": {
		}
		break;
		case "Barny": {
		}
		break;
	}
into the obvious if-then-else chain.

NB: This example assumes an class String() with op== and String(char *)

I guess there is one implicit assumtion in all of this, and that is
that the case statement is clearer than the long list of if-then-elses,
but I take that for granted.  I also realize that this might be taken
as a bit kitchen-sinky.  Still, I think that it would make C++ an
even-better C :-).

					Lewis.



"OS/2: half an operating system for half a computer."

In Real Life:		Lewis Gordon Pringle Jr.
Electronic Mail:	lpringle@labs-n.bbn.com
Phone:			(617) 873-4433

wm@ogccse.ogc.edu (Wm Leler) (12/15/88)

I don't think you could extend switch statements to work on strings,
since there is some ambiguity between whether you want to compare
the strings by comparing their contents (using strcmp) or by comparing
their pointers (a very reasonable thing to do).

wm

akwright@watdragon.waterloo.edu (Andrew K. Wright) (12/16/88)

In article <33528@bbn.COM> lpringle@labs-n.bbn.com (Lewis G. Pringle) writes:
	...  example omitted
>This example reminds me of a suggestion for an extention to C++.  The problem
>with the above code is that switch statements require the cases to be integer
>constants - though there is really no good reason for this.
	...
>When c++ sees a switch statement, it could generate the skip-chain
>of if-then-elseifs from that, so long as the argument to the switch
>and the cases where all =='able.  (As an optimization - of course -
>when the cases are all constant it could just output the C switch statement
>as-is.)

There is one crucial difference between an if-else chain and a C switch:
the case arms of a C switch are checked for duplication.
Allowing switch to operate on data of any type makes this duplication
check impossible at compile time.

An idea i had a few years ago, but have never had the time to test,
is this:  a switch executes *all* cases which match.  This could
be quite useful in many cases, altho code generation for it is a bit
more complex.  It is a bit difficult to fit into C, since C cases dont
really have an "endcase".  But you can imagine one, and consider:

	switch( x ) {
	case 1: case 2:
		code for x == 1 or 2;
	endcase;
	case 2:
		code for x == 2 only;
	endcase;
	case 1: case 2: case 3:
		code for x == 1, 2, or 3;
	endcase;
	}

To code this example in ordinary C you must use two switches (or some ifs).
A C switch allows the flow of control from two case arms to merge, but
not to branch.  this construct allows both merging and branching.

Wierd?  maybe.  if your suggestion was a kitchen sink, this is a
veritable jacuzzi.

Andrew K. Wright      akwright@watmath.waterloo.edu
CS Dept., University of Waterloo, Ont., Canada.

lpringle@bbn.com (Lewis G. Pringle) (12/17/88)

In article <1907@ogccse.ogc.edu> wm@ogccse.UUCP (Wm Leler) writes:
>I don't think you could extend switch statements to work on strings,
>since there is some ambiguity between whether you want to compare
>the strings by comparing their contents (using strcmp) or by comparing
>their pointers (a very reasonable thing to do).
>

You seem to have missed something.  What I suggested was more general
than extending switch to work with strings. The compiler would barf on
strings unless the user had defined the approriate conversion operators
and op== functions.  The user could then define op== however he wanted
(pointer equality or strcmp() equality).

A more interesting problem is one brought up by Michael Tiemann.  He pointed
out that that the result of the evaluation of the switch() would be a
auto temp variable whose op== function would end up called a bunch of
times.  This is kosher, so long as op== is guarenteed not to modify either
of its arguments, but the compiler does not enforce this!

This leads me to another suggestion - which may already be true for all
I know - that whenever you define an op== function (or other comparitor)
it be required to have as aguements const-references.  I kind of like this
idea (I would be unhappy do discover that in some library I was using,
the op== function changed one of the things I was comparing!)  but it
would be a great departure from the current c++ definition where these
comparison operators are not assumed to have any particular semantics.


					Lewis.


"OS/2: half an operating system for half a computer."

In Real Life:		Lewis Gordon Pringle Jr.
Electronic Mail:	lpringle@labs-n.bbn.com
Phone:			(617) 873-4433

pcg@aber-cs.UUCP (Piercarlo Grandi) (12/18/88)

In article <11001@ulysses.homer.nj.att.com> jss@hector.UUCP (Jerry Schwarz) writes:
    
    Also note, that setjmp/longjmp must be used very carefully (if at
    all) in C++ programs because destructors are not called when longjmp
    unwinds the stack.

Old problem to Lispers; in sophisticated Lisps, catch/throw (essentially
equivalent to setjmp/longjmp as means of transferring control non locally)
trigger entry/exit hooks associated to each scope they fly over.

Note that in the general case you do not just want exit hooks when
leaving a block, you also want entry hooks when you enter a block. In
sophisticated languages, a block instance may be exited/reentered several
times... In a very real sense the set of constructors for a block in C++
is the entry hook, and the destructors are the exit hook.

The problem is that it requires new syntax and some hazy code to
generalize this to non local transfers of control.

As a contribution to the discussion on constructors/destructors etc...,
I repost a couple of library components that did not make it out of Europe
when I had posted them. They are both examples of how to use the
constructor/destructor mechanism as generalized entry/exit hooks, to
implement shallow bound dynamic scoping of variables, and tracing
of procedure calls.

Here it goes:

===============================================================================

In  article  <4370@polya.Stanford.EDU> shap@polya.Stanford.EDU (Jonathan
S. Shapiro) writes:

    There  is  at  least one way to do exception handling in the current
    scheme of things. It goes like this:

	[ complicated things deleted... ]

    Now  I  know  that  there are lots and lots of problems here. I also
    know that I don't know a lot about exception handling.

Dear  Jonathan S. Shapiro at least you hit upon the obviously right idea
soon  enough,  something that most people that have dealt with exception
handling have not yet done.

In  an  old paper of mine in an obscure italian journal I argued (I hope
definitively) that exception handling only requires dynamic scoping; non
local control transfers are totally orthogonal to the issue of exception
handling.

Dynamic  scope is useful per se, and in its shallow bound variety can be
implemented  in  a language like Pascal or C in an afternoon (I did that
for  fun  to  the PCC years ago...). Non local control transfers require
rerooting  of  the  control  "tree" (can be a graph) AND the environment
"tree"  (can  be  a  graph);  it  also requires rerooting of the various
"tree"s maintained by the application. This can only be done by allowing
general  entry  and exit functions. The wrapper feature in G++ is a step
in that direction.

I  am  enclosing  an ar(1) tail to this message; it contains a couple of
goodies, one for defining dynamically scoped esception handlers, another
to  do nice function tracing. They demonstrate some nice ideas. They are
useful  but limited; you may think of ways to extend them... I refrained
from doing so because I do not like complicated machinery.

    I  will  also  be  posting  sooner or later a C++ version of a fully
    general  coroutine/generators package of C macros that I perfected a
    couple  of  years. ago. It uses some nifty technique (taking address
    of  labels... Thanks to Steve Johnson for permitting that in PCC and
    to RMS for permitting that in GCC). You will be fazed.

---------------------- CUT HERE -- AR(1) FORMAT FILE FOLLOWS ----------------
!<arch>
Handler.3       591288803   137   20    100600  1844      `
.TH HANDLER 3+ "PCG"
.ad
.SH NAME
handler, handlerin \- redefine for the duration of a block a pointer to proc
.SH SYNOPSIS
.nf
.B "#include <Handler.H>"
.sp
.BI "handler(" procpointer ") = " newvalue ;
.BI "handlerin(" class , op , procpointer ") = " newvalue ;
.fi
.SH DESCRIPTION
These two macros, one for a simple variable, the other for a member of a
class, allow dynamic redefinition of its value locally to a block. The
variable
.B must
have type pointer to procedure. This is most useful to redefine for the
duration of a block an exception handler.
.LP
There are two forms of the macro, one for straight global variables, and the
other one for members of classes. The second form has three parameters, the
name of the structure, the sequence used to get to the member (that is the
path used to get to the member, a sequence of names, possibly empty,
connected by one of
.BR "::" ", " "->" ", " "." ),
the name of the member.
.SH EXAMPLES
.nf
{
    handler(SigSegv) = &InvokeDebugger;

    . . . .
}

{
    handlerin(Stack,::,stackIsEmpty) = &PrintErrorMessage;

    . . . .
}
.SH BUGS
Do not use global variable names prefixed by double colons.
.LP
The first and third parameters of
.B handlerin
are used to build a hopefully unique name for a temporary; if two handlers
have the same name in the same class but are reached via different paths,
such as
.B aclass::astruct.handler
and
.B aclass::bstruct.handler ,
and theyr are redefined in the same block, a clash will result.  This is
what you deserve of course.
.LP
Non local gotos via library procedures will
.B not
restore the previous value; straight non local gotos or returns out of a
block will restore the previous value, as the macros expand to a declaration
of a dummy variable whose constructor puts into it the current value of the
global, and the destructor restores it.
Handler.H       592342047   137   20    100600  3749      `
/*
    Copyright 1988 Free Software Foundation. All rights reserved.

    This  work  of authorship of Piercarlo Grandi is the property of the
    Free  Software  Foundation, and its use and reproduction are subject
    to the terms and conditions detailed in the GNU C++ license.

    This header allows the local redefinition of a pointer to a function
    variable.

    It would be more complicated and inefficient, but possible, to use a
    similar  scheme  for  implementing  dynamic  binding  on any type of
    global  variable  (hint:  pointerToHandler must become a void *, and
    points  to  a chunk of core of the right size...). We have here only
    pointers to procedures. This is what we really need most for now, as
    the  ability to have dynamic scope pointer to procedure variables is
    all one's needs to do exception handling (no, Shaula and Barbara and
    Jean,  non local transfer of control is NOT identical with exception
    handling, though often an exception handler will do it!).

    If  you  are a real sophisticated type you may want then a non local
    goto  implementation that does activate all destructors on exit from
    a scope and constructors on entry. If you do this you have the means
    to  implement  Baker's  environment rerooting... (rumour has it that
    Dr.  Stroustrup  is  haggling  with the problem of how to define non
    local  jumps  in  C++,  we  all  hope  he reads Baker's and Steele's
    classics...).

    In  its limited sophistication this source is just a fix waiting for
    Saint  Michael  Tiemann to implement the following extension to C++,
    that  whenever a global is declared in a block as "extern auto", its
    value  is saved on the stack at entry to the block and restored from
    the  stack on exit. This is of course a shallow bound implementation
    of  dynamic  scoping...  I  did  this extension to the Vax PCC in an
    afternoon  (note, I have lost the modifications years ago, don't ask
    me for a copy).

    The fix is useful though, as tipically the function pointer variable
    will  be  used  to point to an exception handler, and this macro and
    type  definition  allow redefition local to a block of the exception
    handler. Example:

    extern void abort();
    Handler OnUnderflow = (Handler) &abort;

   	   	. . . .

    inline float ReturnZero() { return 0.0; }

    float SomeFunction
    (
   	float anArg
    )
    {
   	handler(OnUnderflow) =  (Handler) &ReturnZero;


   	{
   	    handler(OnUnderflow) = (Handler) &abort;

   	   	. . . .
   	}

		. . . .
    }
*/


typedef void		(*Handler)(void *);

class HandlerFrame
{
private:

    Handler		    *pointerToHandler;
    Handler	   	    previousHandler;

public:

    inline		    HandlerFrame
    (
   	register Handler   	*pointerToHandler
    )
    {
	this->pointerToHandler	    = pointerToHandler;
   	this->previousHandler	    = *pointerToHandler;
    }

    inline void		    ~HandlerFrame
    (
    )
    {
   	*(this->pointerToHandler)   = this->previousHandler;
    }
};


/*
    We  need two different macros, one for ordinary global handlers, the
    other  for  handlers that are members of classes. In the latter case
    the  user  must break up the reference to the member in three parts,
    the naem of class, operator used to get to the member (:: -> .), and
    the name of member.

    Note  that we MUST put in underscores; think of classes TreeBig with
    member Apple and class Tree with member BigApple...
*/

#define handler(name)							\
    HandlerFrame Handler_##name(& (Handler) (name));			\
    name

#define handlerin(class,member,name)					\
    HandlerFrame Handler_##class##_##name(& (Handler) (class member name));\
    class member name

HandlerTest.C   591286103   137   20    100600  2870      `
#include "Handler.H"

class Small;
extern void		abort();
extern int	   	puts(const char *);
extern int		printf(const char * ...);

class Small
{
private:

    signed char		    value;

public:

   static Small		    (*onOverflow)();

    /* constructor */	    Small();
    /* initializer */	    Small(Small &);
    /* constructor */ 	    Small(const int);
    /* converter */	    operator int();

    friend Small	    operator +(const Small &,const Small &);
    friend Small	    operator -(const Small &,const Small &);
    friend Small	    operator /(const Small &,const Small &);
    friend Small	    operator *(const Small &,const Small &);
};

inline 			Small::Small
(
)
{
    this->value = '\0';
}

inline 			Small::Small
(
    Small		    &initial
)
{
    value = initial.value;
}

inline 			Small::Small
(
    const int		    initial
)
{
    value = initial;

    printf("Constructor initial %d, value %d\n",initial,this->value);

    if (this->value != initial)
	this->value = (*Small::onOverflow)();
}

inline int   	   	Small::operator int
(
)
{
    return value;
}

inline Small		operator +
(
    register const Small    &a,
    register const Small    &b
)
{
    register const Small    c((int) a.value + (int) b.value);

    return c;
}

inline Small		operator -
(
    register const Small    &a,
    register const Small    &b
)
{
    register const Small    c((int) a.value - (int) b.value);

    return c;
}

inline Small		operator *
(
    register const Small    &a,
    register const Small    &b
)
{
    register const Small    c((int) a.value * (int) b.value);

    return c;
}

inline Small	   	operator /
(
    register const Small    &a,
    register const Small    &b
)
{
    return (b.value != '\0')
	? (int) a.value / (int) b.value
	: (*Small::onOverflow)();
}

static Small		Terminate
(
)
{
    puts("Terminating because of overflow!");
    abort();

    *(char *) 0;

    return '\0';
}

static Small	   	Oops
(
)
{
    puts("You naughty boy! you overflowed, but you get a reprieve.");

    return 127;
}

int			main
(
    int	   	   	    argc,
    char   	   	    *(argv[]),
    char   	   	    *(envp[])
)
{
    Small::onOverflow = &Terminate;

    printf("sizeof (Small) %u\n",(unsigned) sizeof (Small));

    Small	   	    a(100);
    Small  	   	    b(60);
    Small  	   	    c;

    printf("After declarations a %d, b %d, c %d\n",(int) a,(int) b,(int) c);

    {
	handlerin(Small,::,onOverflow)	    = &Oops;

	printf("Small::onOverflow 0x%#lx, Terminate 0x%#lx, Oops 0x%#lx\n",
	    (long) Small::onOverflow, (long) &Terminate, (long) &Oops);

	c = a + b;

	printf(" c %d = a %d + b %d\n",(int) c,(int) a,(int) b);
    }

    printf("Small::onOverflow 0x%#lx, Terminate 0x%#lx, Oops 0x%#lx\n",
   	(long) Small::onOverflow, (long) &Terminate, (long) &Oops);

    c = a + b;

    printf(" c %d = a %d + b %d\n",(int) c,(int) a,(int) b);
}
Trace.3         592524030   137   20    100600  2473      `
.TH TRACE 3+ "PCG"
.ad
.SH NAME
Trace, TraceOF(), TraceDO() \- trace functions on entry, body, exit.
.SH SYNOPSIS
.nf
.B "#include <Trace.H>"
.sp
.BI "Trace aname(" string ");"
.IB "aname" "(" string ");"
.sp
.BI "TraceOF(" string ");"
.BI "TraceDO(" expression ");"
.fi
.SH DESCRIPTION
A new class is defined,
.I Trace
whose object may be used to trace function entry/exit. The constructor
of a Trac eobject will expect a string, the contents of the object;
an indentation level is incremented by the constructor, and that string
(which ought to be the name of the function) will be printed, with
an indication that that function has been entered. On exit from
the function the destructor will be automatically called, which will
print the string of the trace object and a note that the function
has been exited. When a Trace object is applied to a string, the
string in it will be printed (after suitable indentation), followed
by the string being supplied as a parameter.
.PP
Of course you can have any block traced in this way; it will be however
not very clever to declared more than object of class Trace
per block...
.PP
The address of the
.I stream
object you want to use for tracing (usually
.IR cerr )
will have to be put into
.IR "Trace::traceStream" ;
if this variable is zero then the trace will be suppressed.
.PP
An example of use:
.PP
.nf
#include <Trace.H>
#include <stream.h>

int main
(
    int argc,
    char **argv,
    char **envp
)
{
    Trace::traceStream = &cerr;

    Trace trace("main");

    trace("going to print a greeting...");
    cout << "Hello world!\en";
    trace(" greeting printed.");

    return 0;
}
.fi
.PP
Normally however you will want to use a couple of macros that expand
the tracing statements only if the macro
.I DEBUG
has been defined.
.PP
the macro
.I TraceOF
will then expand to just a declation of a Trace object; the only
parameter to TraceOF will be supplied to the Trace constructor.
.I TraceDO
is a macro with a single parameter; the parameter will be expanded
verbatim only if DEBUG is defined. You will tipically use these
macros like this:
.PP
.nf
#define DEBUG // or usually with -DDEBUG to the compiler...

#include <Trace.H>
#include <stream.h>

int main
(
    int argc,
    char **argv,
    char **envp
)
{
    Trace::traceStream = &cerr;

    TraceOF("main");

    TraceDO(cerr << "going to print a greeting...");
    cout << "Hello world!\en";
    TraceDO(cerr << " greeting printed.");

    return 0;
}
.fi

Trace.H         592523499   137   20    100600  1764      `
/*

    This  class  is  intended  to  be  used  to  trace  procedures.  The
    constructor  will  have  the procedure name as argument, and it will
    print  something  to the effect that the procedure has been entered,
    increment  the  call  level  counter,  and  store  the  name  of the
    procedure. The destructor will decrement the level counter and print
    that the procedure has been exited.

    In  between  the  ()  operator  can  be  called to give personalized
    tracing statements at the right level.

    Example:

    float Square(register const float x)
    {
   	Trace trace("Square");

   	trace("x = %f\n", (float) x);

   	float y = x * x;

   	trace("x^2 = %f\n", (float) y);

   	return y;
    }

    Normally we will want to conditionally expand these declarations and
    macro  calls  are  thus  provided  for this purpose, to be used like
    this:

    float Square(register const float x)
    {
   	TraceOF("Square");

   	TraceDO(("x = %f\n", (float) x));

   	float y = x * x;

   	TraceDO(("x^2 = %f\n", (float) y));

   	return y;
    }

*/

#ifdef DEBUG
#   define TraceOF(name)    Trace TraceThis(name)
#   define TraceDO(args)   (TraceThis(), (args))
#else
#   define TraceOF(name)
#   define TraceDO(args)
#endif

class Trace
{
private:

    static short unsigned   callLevel = 0;
    char		    *blockName;

public:

    static void		    *traceStream = 0;

       	   	   	    Trace(const char *);
    void   	   	    ~Trace();

    int   	   	    operator ()(const char * =": ");
};

inline			Trace::Trace
(
    const char		    *blockName
)
{
    this->blockName = blockName;

    (*this)(" ENTERED\n");
    Trace::callLevel++;
}

inline		   	Trace::~Trace
(
)
{
    --Trace::callLevel;
    (*this)(" EXITED\n");
}
Trace.C         592523039   137   20    100600  793       `
#ifdef EXAMPLE
#   define DEBUG
#endif

#include <stream.h>

#include "Trace.H"

int		   	Trace::operator ()
(
    const char	   	    * const aLabel
)
{
    if (traceStream == 0)
	return;

    {
   	register unsigned	indent;


   	for (indent = callLevel; indent != 0; --indent)
	    ((ostream *) traceStream)->put(' ');
    }

    (*(ostream *) traceStream) << this->blockName << aLabel;

    return callLevel;
}


#ifdef EXAMPLE

float Square(register const float x)
{
    TraceOF("Square");

    TraceDO(cerr << "x = " << x << "\n");

    float y = x * x;

    TraceDO(cerr << "x^2 = " << y << "\n");

    return y;
}

int main(int argc, char **argv, char **envp)
{
    Trace::traceStream = &cout;

    TraceOF("main");

    {
	TraceOF("a block");

	return int(Square(3.0));
    }
}
#endif
-- 
Piercarlo "Peter" Grandi			INET: pcg@cs.aber.ac.uk
Sw.Eng. Group, Dept. of Computer Science	UUCP: ...!mcvax!ukc!aber-cs!pcg
UCW, Penglais, Aberystwyth, WALES SY23 3BZ (UK)