TITLE: is the preprocessor dead in C++? (Newsgroups: comp.lang.c++.moderated, 22 Mar 98) AUTHOR: herbs@cntc.com (Herb Sutter) .--------------------------------------------------------------------. | Guru of the Week problems and solutions are posted regularly on | | news:comp.lang.c++.moderated. For past problems and solutions | | see the GotW archive at http://www.cntc.com. | | Is there a topic you'd like to see covered? mailto:herbs@cntc.com | `--------------------------------------------------------------------' _______________________________________________________ GotW #32: Preprocessor Macros Difficulty: 4 / 10 _______________________________________________________ >With flexible features like overloading and type-safe >templates available, why would a C++ programmer ever >write "#define"? C++ features often, but not always, cancel out the need for using #define. For example, "const int c = 42;" is superior to "#define c 42" because it provides type safety, avoids accidental preprocessor edits, and other reasons. There are, however, still a few good reasons to write #define: 1. Header Guards This is the usual trick to prevent multiple header inclusions: #ifndef MYPROG_X_H #define MYPROG_X_H // ... the rest of the header file x.h goes here... #endif 2. Accessing Preprocessor Features It's often nice to insert things like line numbers and build times in diagnostic code. An easy way to do this is to use predefined macros like __FILE__, __LINE__, __DATE__ and __TIME__. For the same and other reasons, it's often useful to use the stringizing and token-pasting preprocessor operators (# and ##). 3. Selecting Code at Compile Time (or, Build-Specific Code) This is the richest, and most important, category of uses for the preprocessor. Although I am anything but a fan of preprocessor magic, there are things you just can't do as well, or at all, in any other way. a) Debug Code Sometimes you want to build your system with certain "extra" pieces of code (typically debugging information) and sometimes you don't: void f() { #ifdef MY_DEBUG cerr << "some trace logging" << endl; #endif // ... the rest of f() goes here... } It is possible to do this at run time, of course. By making the decision at compile time, you avoid both the overhead and the flexibility of deferring the decision until run time. b) Platform-Specific Code Usually it's best to deal with platform-specific code in a factory for better code organization and runtime flexibility. Sometimes, however, there are just too few differences to justify a factory, and the preprocessor can be a useful way to switch optional code. c) Variant Data Representations A common example is that a module may define a list of error codes, which outside users should see as a simple enum with comments, but which inside the module should be stored in a map for easy lookup. That is: // For outsiders: enum Errors { ERR_OK = 0, // No error ERR_INVALID_PARAM = 1 // ... } // For the module's internal use: map lookup; lookup.insert( make_pair( Error(0), "No error" ) ); lookup.insert( make_pair( Error(1), "" ) ); ... We'd like to have both representations without defining the actual information (code/msg pairs) twice. With macro magic, we can simply write a list of errors as follows, creating the appropriate structure at compile time: DERR_ENTRY( ERR_OK, 0, "No error" ), DERR_ENTRY( ERR_INVALID_PARAM, 1, "" ), //... The implementations of DERR_ENTRY and related macros is left to the reader. These are three common examples; there are many more. (Private mail, 31 Mar 98) TRIBBLE: David R Tribble An example or two here might be instructive. So, here's a few things that are easy to do using the preprocessor but not so easy without it... // 1. void putword(ostream &out, int w) { // Write the bytes of 'w' to stream 'out' in portable form #if LITTLE_ENDIAN ... code which does byte swapping ... #else ... code which does not do byte swapping ... #endif } // 2. #include #if INT_MAX >= 2147483647 typedef myInt32 int; #else typedef myInt32 long; #endif // 3. int myFunc(int arg) { #if is_not_yet_complete ... 90% complete code here ... #else // Fake it for now return 1; #endif }