The C++ Programming Language

Part 12: The Preprocessor

The Preprocessor was inherited from the C programming language. It parses our code before the compiler and generates the final stream that will be compiled. Using its features we can perform some modifications to our code right before compilation.

Preprocessor directives

Preprocessor directives begin with hash symbol (#). This character must be the first of the line, but not necessarily on the first column. Then follows the directive keyword. Blanc space between the hash symbol and the keyword is allowed. Here is a list of the preprocessor directives:

#include

This is the first preprocessor directive we learn as C/C++ developers. This instructs the preprocessor to include or inject the contents of another file at the current location. We mainly use it to include a header file with declarations.

#define

This directive is used to define a new name or macro. This macro can be used to modify the code the preprocessor outputs. Whenever the preprocessor encounters the macro, replaces it with its value:

#define PI 3.1415926

int main() {
    double radius = 1.234;
    double circumference = 2 * PI * radius;
}

It is a good way to replace difficult to remember values with easy to read and remember names. If we ever decide to increase the accuracy by adding digits to PI we change the definitions and recompile.

It can be used just to define a macro without giving it a value

#define __DEBUG__

#ifdef / #ifndef / #else / #endif

Check if a compiler directive has been defined and take appropriate action.

#include <iostream>

#define __DEBUG__

int main() {
#ifdef __DEBUG__
    std::cout << "debug enabled\n";
#else
    std::cout << "release enabled\n";
#endif
}

We can use this to safeguard our headers so they do not load twice:

#ifndef __PREPROC_H__
#define __PREPROC_H__

// this will not lead to recursion!!
// #ifndef in the first line prevents it
#include "preproc.h"

#endif // __PREPROC_H__

This is a very common situation in large projects where one header loads another and so on until they make a circle and compilation breaks because it sees the same definitions again.

#error

This directive raises a compilation error and stops the compilation process:

#ifndef __PREPROC_H__
#error include preproc.h to proceed
#endif

#pagma

Issue a special command to the compiler or the linker. The example dictates the compiler to ignore a specific warning and the linker to link our code with OpenGL:

#pragma warning( disable : 4705 )
#pragma comment( lib, "opengl32.lib" )

Predefined preprocessor macros

The preprocessor has some built-in macros we can access in our code. They can be particularly useful when displaying error messages which can be enhanced with details about their location within our code.

__LINE__: an integer containing the current line of code

__FILE__: a string containing the current file name

__DATE__: a string containing the compilation date in the form ‘MMM DD YYYY’

__TIME__ : a string containing the compilation time in the form ‘hh:mm:ss’

__cplusplus: it is a long integer holding the version of the C++ standard the compiler supports. Possible values are:

This macro is extremely useful when we want to interface C++ with C code:

#ifdef __cplusplus
// the following definitions follow the C standard
// note the opening bracket, look for the closing bracket
extern "C" {
#endif
    // function that can be called both from C and C++ code
    void some_function();
#ifdef __cplusplus
    // end of C type definitions
    // after the bracket we have C++ style definitions
}
// C++ definitions can follow (always inside #ifdef/#endif block!)
class some_class {
public:
    some_class() {}
};
#endif

Summary

In this part we tried to give a brief description of the preprocessor. A valuable tool that if used correctly can help us solve many problems.

The C++ Programming Language