C++

Preprocessor & Macros

10 question(s)

What is the C++ preprocessor?

Beginner
The preprocessor is a text-processing step that runs before compilation, handling directives beginning with # : #include (textual inclusion of headers), #define (macros), and conditional compilation (#if/#ifdef/#endif). It manipulates source text, not C++ semantics, so it has no knowledge of types or scope.
#include <vector>      // textual inclusion
#define PI 3.14159       // macro substitution
Real-world example The preprocessor stitches headers into each translation unit before the compiler proper runs.

Common follow-ups: When does the preprocessor run? | Does it understand C++ types?

Compilation & Linking Preprocessor & Macros Preprocessor & Macros

What is the difference between #include with angle brackets and quotes?

Beginner
#include <name> searches the system/standard include directories (used for standard and library headers). #include "name" searches the current directory first (then the system paths), used for your project's own headers. The distinction guides where the compiler looks for the file.
#include <string>     // system/library header
#include "myclass.h"   // project header
Real-world example Project headers use quotes so the compiler finds them relative to the source, while std headers use angle brackets.

Common follow-ups: Where does each form search? | Which is for your own headers?

Compilation & Linking Preprocessor & Macros Preprocessor & Macros

What is an include guard and why is it needed?

Beginner
An include guard prevents a header's contents from being included more than once in a translation unit, which would cause redefinition errors. It uses #ifndef/#define/#endif around the header body, or the non-standard-but-widely-supported #pragma once. Guards are essential because headers are often included transitively multiple times.
#ifndef MYHEADER_H
#define MYHEADER_H
// ... declarations ...
#endif
Real-world example Without an include guard, a widely-included header causes 'redefinition' errors when pulled in twice.

Common follow-ups: What does #pragma once do? | Why can a header be included multiple times?

Compilation & Linking Preprocessor & Macros Preprocessor & Macros

Why are function-like macros discouraged in modern C++?

Intermediate
Function-like macros do blind text substitution with no type checking, no scope, and pitfalls like multiple argument evaluation and operator-precedence bugs. Modern C++ replaces them with inline functions, templates, and constexpr, which are type-safe, respect scope, and debuggable. Macros remain only for things the language can't do (conditional compilation, stringizing).
#define SQ(x) ((x)*(x))
SQ(i++)   // i incremented twice! use a constexpr function instead
Real-world example A MAX macro double-evaluates its arguments causing a subtle bug; a template function fixes it.

Common follow-ups: What is the double-evaluation problem? | What replaces macros?

Constexpr & Compile-Time Templates Preprocessor & Macros

What are conditional compilation directives used for?

Intermediate
#if/#ifdef/#ifndef/#else/#endif include or exclude code at compile time based on macros—used for platform-specific code, feature toggles, debug/release differences, and include guards. They let one codebase compile differently for different targets or configurations.
#ifdef _WIN32
    // Windows-specific code
#else
    // POSIX code
#endif
Real-world example Platform-specific file paths are selected via #ifdef _WIN32 so one codebase builds on Windows and Linux.

Common follow-ups: What is a common use of #ifdef? | How do feature toggles use it?

Compilation & Linking Preprocessor & Macros Preprocessor & Macros

What are the # (stringize) and ## (token paste) operators?

Advanced
In a macro, # turns a parameter into a string literal (stringizing), and ## concatenates two tokens into one (token pasting). They enable macro tricks like generating names or debug output, but are advanced and easy to misuse. They're used in logging/assert macros and code-generation patterns.
#define STR(x) #x            // STR(hi) -> "hi"
#define CAT(a,b) a##b          // CAT(foo,bar) -> foobar
Real-world example An assert-style macro uses # to embed the failing expression's text in the error message.

Common follow-ups: What does # do? | What does ## do?

Preprocessor & Macros Strings & string_view Preprocessor & Macros

What are predefined macros like __LINE__ and __FILE__?

Beginner
The preprocessor provides built-in macros: __LINE__ (current line number), __FILE__ (source file name), __DATE__/__TIME__ (build date/time), __func__ (function name), and __cplusplus (the C++ standard version). They're commonly used in logging, assertions, and diagnostics to report where events occurred.
std::cout << __FILE__ << ":" << __LINE__ << "\n";
Real-world example A logging macro embeds __FILE__ and __LINE__ so each message shows where it was emitted.

Common follow-ups: What does __cplusplus indicate? | Where are these used?

Preprocessor & Macros Compilation & Linking Preprocessor & Macros

How does #pragma once compare to traditional include guards?

Advanced
#pragma once tells the compiler to include a file only once, achieving the same effect as #ifndef guards with less boilerplate and no risk of guard-name collisions. It's non-standard but supported by all major compilers. Its rare edge case is with the same file reachable via different paths/symlinks; traditional guards are fully portable.
#pragma once     // top of header, instead of #ifndef guards
Real-world example Most modern headers use #pragma once for brevity, falling back to guards only where strict portability is required.

Common follow-ups: What is the advantage of #pragma once? | What is its edge case?

Compilation & Linking Preprocessor & Macros Preprocessor & Macros

What is the difference between a macro constant and a constexpr/const constant?

Intermediate
A #define constant is untyped preprocessor text substitution with no scope, no type checking, and no debug symbol. A const/constexpr constant is a typed, scoped variable known to the compiler and debugger, participating in overload resolution and type safety. Modern C++ strongly prefers constexpr/const over #define for constants.
#define MAX 100          // untyped text
constexpr int Max = 100;   // typed, scoped, debuggable
Real-world example Replacing #define constants with constexpr gives type safety and lets them appear in the debugger.

Common follow-ups: Why prefer constexpr over #define? | Do macros have scope?

Constexpr & Compile-Time Preprocessor & Macros Preprocessor & Macros

What are the dangers of macros affecting names and how do you mitigate them?

Advanced
Because macros do global text replacement ignoring scope, a macro like #define max(...) can clobber legitimate identifiers (e.g., std::max, member functions), causing baffling errors. Mitigations: avoid macros where possible, use ALL_CAPS naming to signal macros, #undef them promptly, and be wary of platform headers (like windows.h) that define common names.
// windows.h defines min/max macros that break std::min/std::max
#define NOMINMAX   // before including windows.h
Real-world example windows.h's min/max macros break std::min until NOMINMAX is defined, a classic macro-name collision.

Common follow-ups: Why do macros ignore scope? | How do you prevent macro name clashes?

Namespaces Compilation & Linking Preprocessor & Macros