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)