Ch3.11: Preprocessor

What the Preprocessor Is

Before the compiler sees your C++ code, it is first processed by the preprocessor. The preprocessor performs simple text transformations on your source code. It does not understand C++ types, scopes, or semantics. It only manipulates tokens.

Preprocessor directives begin with # and are handled line-by-line before compilation.

#include

The #include directive literally copies the entire contents of another file into the current file.


#include <fast_io.h>
#include <fast_io.h>   // included twice

Why this does not break compilation

The fast_io library uses #pragma once at the top of every header. When the preprocessor sees the header the second time, it ignores its contents entirely.

This prevents duplicate definitions and makes repeated inclusion safe.

What happens without #pragma once

If a header does not use #pragma once (or an include guard), including it twice inserts the same text twice:


// myheader.h (no pragma once)
void f();

#include "myheader.h"
#include "myheader.h"   // inserted twice → duplicate definition error

This breaks compilation because the function is defined twice.

Why #include Is Slow

Because #include performs a full copy‑paste of the header text, including all of its own #include directives, large projects can become slow to compile.

A single header may expand into thousands of lines of code, and this expansion happens in every translation unit that includes it.

#pragma once

#pragma once ensures that a header file is included only once per translation unit.


// myheader.h
#pragma once

void f();

This prevents duplicate definitions and speeds up compilation. All major compilers support it.

#define

The #define directive creates a macro. Macros are simple text substitutions performed before compilation.

Object-like macros


#define PI 3.141592653589793
#define BUFFER_SIZE 1024

When the preprocessor sees PI, it replaces it with the text 3.141592653589793.

Function-like macros


#define SQR(x) ((x) * (x))

Parentheses are essential to avoid precedence bugs.

Macro side effects

Macros do not evaluate arguments once. They substitute text. This can cause dangerous side effects:


::std::uint_least32_t i{};

println(SQR(++i));

The macro expands to:


((++i) * (++i))

This increments i twice, which is almost never what you want.

Warnings about macros

Macros are still necessary for conditional compilation and include guards.

#embed

The #embed directive embeds the contents of a file directly into your program as data at compile time.


static constexpr auto data =
#embed "resource.bin";

Unlike #include, which pastes C++ code, #embed is used for binary or text resources.

Support for #embed depends on the compiler and standard version.

Preprocessor Directives Summary


#include        // copy-paste another file
#pragma once    // include only once
#define         // define a macro
#embed          // embed external data
#ifdef/#ifndef  // conditional compilation (next chapter)
#endif
      

Key takeaways