All Courses
Foundations

Preprocessor Directives & Namespaces in C++


What is the Preprocessor

The preprocessor runs BEFORE the compiler. It processes directives (lines starting with #) and transforms your source code.

Compilation Process with Preprocessor

┌─────────────────────────────────────────────────────────────────────────────┐
│                         COMPILATION STAGES                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Source Code (.cpp)                                                         │
│        │                                                                     │
│        ▼                                                                     │
│   ┌────────────────┐                                                         │
│   │  PREPROCESSOR  │  ◄── Handles #include, #define, #ifdef, etc.           │
│   │                │      Expands macros, includes headers                   │
│   └───────┬────────┘                                                         │
│           │                                                                  │
│           ▼                                                                  │
│   ┌────────────────┐                                                         │
│   │    COMPILER    │  ◄── Converts to assembly/object code                  │
│   └───────┬────────┘                                                         │
│           │                                                                  │
│           ▼                                                                  │
│   ┌────────────────┐                                                         │
│   │    LINKER      │  ◄── Combines object files, resolves symbols           │
│   └───────┬────────┘                                                         │
│           │                                                                  │
│           ▼                                                                  │
│   Executable (.exe, .out)                                                    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Include Directives

#include

Copies the contents of a file into your source code.

// System headers (standard library)
#include <iostream>      // Searches system include paths
#include <vector>
#include <string>

// User headers (your own files)
#include "myheader.h"    // Searches current directory first
#include "utils/helper.h"

// What happens:
// The preprocessor literally copies the entire content
// of the header file into your source file at that point

Include Guards (Prevent Multiple Inclusion)

// myheader.h - Traditional include guard
#ifndef MYHEADER_H       // If not defined
#define MYHEADER_H       // Define it

class MyClass {
    // ...
};

#endif // MYHEADER_H     // End of guard


// myheader.h - Modern pragma (preferred)
#pragma once             // Simpler, supported by all major compilers

class MyClass {
    // ...
};

How Include Guards Work

┌─────────────────────────────────────────────────────────────────────────────┐
│                      INCLUDE GUARD MECHANISM                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Without guards:                         With guards:                        │
│                                                                              │
│  main.cpp:                               main.cpp:                           │
│  #include "a.h"  → class A {...}         #include "a.h" → class A {...}     │
│  #include "b.h"  → #include "a.h"        #include "b.h" → #include "a.h"    │
│                    → class A {...} ❌                     → (skipped) ✓     │
│                      DUPLICATE!                            → class B {...}  │
│                    → class B {...}                                          │
│                                                                              │
│  Error: redefinition of 'class A'        Compiles successfully!             │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Macros

Object-like Macros (Constants)

#define PI 3.14159265359
#define MAX_SIZE 100
#define APP_NAME "MyApplication"
#define DEBUG_MODE

double area = PI * radius * radius;
int buffer[MAX_SIZE];

Function-like Macros

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))

int result = SQUARE(5);     // Expands to: ((5) * (5))
int bigger = MAX(10, 20);   // Expands to: ((10) > (20) ? (10) : (20))

⚠️ Macro Pitfalls

// BAD: Missing parentheses
#define BAD_SQUARE(x) x * x

int a = BAD_SQUARE(2 + 3);  // Expands to: 2 + 3 * 2 + 3 = 11 (not 25!)

// GOOD: Proper parentheses
#define GOOD_SQUARE(x) ((x) * (x))

int b = GOOD_SQUARE(2 + 3); // Expands to: ((2 + 3) * (2 + 3)) = 25 ✓


// BAD: Side effects with macros
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int i = 5;
int result = MAX(i++, 3);   // i++ evaluated TWICE if i > 3!

// SOLUTION: Use inline functions or constexpr instead
inline int safeMax(int a, int b) { return a > b ? a : b; }

Stringification and Token Pasting

// # - Stringification (convert to string literal)
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

cout << STRINGIFY(Hello World);  // Prints: Hello World
cout << TOSTRING(123);           // Prints: 123

// ## - Token pasting (concatenate tokens)
#define CONCAT(a, b) a##b
#define MAKE_VAR(n) var##n

int MAKE_VAR(1) = 10;   // Creates: int var1 = 10;
int MAKE_VAR(2) = 20;   // Creates: int var2 = 20;
int CONCAT(my, Var) = 30;  // Creates: int myVar = 30;

Predefined Macros

cout << "File: " << __FILE__ << endl;      // Current filename
cout << "Line: " << __LINE__ << endl;      // Current line number
cout << "Date: " << __DATE__ << endl;      // Compilation date
cout << "Time: " << __TIME__ << endl;      // Compilation time
cout << "Function: " << __func__ << endl;  // Current function name

// C++ version
#if __cplusplus >= 202002L
    cout << "C++20 or later" << endl;
#elif __cplusplus >= 201703L
    cout << "C++17" << endl;
#elif __cplusplus >= 201402L
    cout << "C++14" << endl;
#elif __cplusplus >= 201103L
    cout << "C++11" << endl;
#endif

Undefine Macros

#define TEMP_VALUE 100

// Use TEMP_VALUE...

#undef TEMP_VALUE  // Remove the macro definition

// TEMP_VALUE is no longer defined

Conditional Compilation

#if, #elif, #else, #endif

#define DEBUG_LEVEL 2

#if DEBUG_LEVEL == 0
    // No debugging
#elif DEBUG_LEVEL == 1
    #define LOG(msg) cout << msg << endl
#elif DEBUG_LEVEL >= 2
    #define LOG(msg) cout << __FILE__ << ":" << __LINE__ << " " << msg << endl
#else
    #define LOG(msg)
#endif

#ifdef, #ifndef

// #ifdef - if defined
#ifdef DEBUG
    cout << "Debug mode enabled" << endl;
#endif

// #ifndef - if NOT defined
#ifndef RELEASE
    cout << "Not in release mode" << endl;
#endif

// Equivalent to:
#if defined(DEBUG)
    // ...
#endif

#if !defined(RELEASE)
    // ...
#endif

Platform-Specific Code

#if defined(_WIN32) || defined(_WIN64)
    #include <windows.h>
    #define PLATFORM "Windows"
#elif defined(__linux__)
    #include <unistd.h>
    #define PLATFORM "Linux"
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    #define PLATFORM "macOS"
#else
    #define PLATFORM "Unknown"
#endif

cout << "Running on: " << PLATFORM << endl;

Feature Toggles

// Enable/disable features at compile time
#define ENABLE_LOGGING
#define ENABLE_PROFILING
// #define ENABLE_EXPERIMENTAL  // Commented out = disabled

#ifdef ENABLE_LOGGING
void log(const string& msg) {
    cout << "[LOG] " << msg << endl;
}
#else
void log(const string& msg) { }  // Empty function
#endif

#ifdef ENABLE_PROFILING
    #define PROFILE_START auto _start = chrono::high_resolution_clock::now()
    #define PROFILE_END cout << chrono::duration<double>(chrono::high_resolution_clock::now() - _start).count() << "s" << endl
#else
    #define PROFILE_START
    #define PROFILE_END
#endif

Pragmas

Compiler-specific directives:

// Disable specific warning
#pragma warning(disable: 4996)  // MSVC
#pragma GCC diagnostic ignored "-Wunused-variable"  // GCC/Clang

// Pack structures (control memory alignment)
#pragma pack(push, 1)  // 1-byte alignment
struct PackedStruct {
    char a;    // 1 byte
    int b;     // 4 bytes
    char c;    // 1 byte
};  // Total: 6 bytes (not 12 with padding)
#pragma pack(pop)

// Region markers (IDE feature)
#pragma region MySection
// Code here
#pragma endregion

// Include once (alternative to include guards)
#pragma once

// Message during compilation
#pragma message("Compiling with DEBUG enabled")

Namespaces

Namespaces prevent name collisions by organizing code into logical groups.

Namespace Basics

// Define a namespace
namespace Math {
    const double PI = 3.14159;

    double square(double x) {
        return x * x;
    }

    double cube(double x) {
        return x * x * x;
    }
}

namespace Physics {
    const double PI = 3.14159265359;  // More precise PI, no conflict!

    double velocity(double distance, double time) {
        return distance / time;
    }
}

// Using namespace members
int main() {
    // Fully qualified names
    double area = Math::PI * Math::square(5);
    double v = Physics::velocity(100, 10);

    return 0;
}

Namespace Visualization

┌─────────────────────────────────────────────────────────────────────────────┐
│                        NAMESPACES PREVENT COLLISIONS                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Without namespaces:                                                         │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  void print() { ... }    // From library A                           │   │
│  │  void print() { ... }    // From library B  ❌ ERROR: redefinition   │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  With namespaces:                                                            │
│  ┌────────────────────────┐    ┌────────────────────────┐                   │
│  │  namespace LibraryA {  │    │  namespace LibraryB {  │                   │
│  │    void print() {...}  │    │    void print() {...}  │                   │
│  │  }                     │    │  }                     │                   │
│  └────────────────────────┘    └────────────────────────┘                   │
│                                                                              │
│  LibraryA::print();  // Calls A's print  ✓                                  │
│  LibraryB::print();  // Calls B's print  ✓                                  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

using Declarations

// using declaration - bring ONE name into scope
using std::cout;
using std::endl;
using std::string;

cout << "Hello" << endl;  // No std:: needed

// using directive - bring ALL names from namespace
using namespace std;  // Now all std names available

vector<int> v;  // No std:: needed
string s;       // No std:: needed

⚠️ using namespace std - The Debate

// In .cpp files: Usually OK, especially for small programs
using namespace std;

// In header files: NEVER! Pollutes all files that include the header
// BAD: myheader.h
#pragma once
using namespace std;  // ❌ Forces this on everyone who includes!

// GOOD: myheader.h
#pragma once
#include <string>
std::string getName();  // ✓ Explicit, no pollution


// Best practice: Use specific using declarations
using std::cout;
using std::vector;
using std::string;

Nested Namespaces

// Traditional (C++11)
namespace Company {
    namespace Product {
        namespace Module {
            void function() { }
        }
    }
}

// C++17 simplified syntax
namespace Company::Product::Module {
    void function() { }
}

// Usage
Company::Product::Module::function();

// Namespace alias for convenience
namespace CPM = Company::Product::Module;
CPM::function();

Anonymous (Unnamed) Namespaces

// Anonymous namespace - internal linkage (like static)
namespace {
    int helperVariable = 42;  // Only visible in this file

    void helperFunction() {   // Only visible in this file
        // ...
    }
}

// Equivalent to:
static int helperVariable = 42;
static void helperFunction() { }

// Use for file-private implementation details

Inline Namespaces (C++11)

// For library versioning
namespace MyLibrary {
    inline namespace v2 {  // Current default version
        void function() { cout << "v2" << endl; }
    }

    namespace v1 {
        void function() { cout << "v1" << endl; }
    }
}

MyLibrary::function();      // Uses v2 (inline namespace)
MyLibrary::v1::function();  // Explicitly uses v1
MyLibrary::v2::function();  // Explicitly uses v2

Recommended Conventions

✅ Do

// 1. Use #pragma once for include guards
#pragma once

// 2. Use constexpr/const instead of #define for constants
constexpr double PI = 3.14159;
const int MAX_SIZE = 100;

// 3. Use inline functions instead of function-like macros
inline int square(int x) { return x * x; }

// 4. Use namespaces to organize code
namespace MyProject {
    // Your code here
}

// 5. Use namespace aliases for long names
namespace fs = std::filesystem;

// 6. Limit using declarations to .cpp files
using std::cout;
using std::endl;

❌ Don't

// 1. Don't use using namespace in headers
// header.h
using namespace std;  // ❌ BAD!

// 2. Don't use macros when alternatives exist
#define MAX_VALUE 100  // Use constexpr instead

// 3. Don't create overly complex macros
#define COMPLEX_MACRO(a, b, c, d) /* hard to debug */

// 4. Don't forget include guards
// Missing #pragma once leads to redefinition errors

// 5. Don't use reserved names (double underscore)
#define __MY_MACRO  // ❌ Reserved for implementation
#define _MyMacro    // ❌ Reserved when followed by uppercase

Cheat Sheet

// Include directives
#include <system_header>
#include "user_header.h"
#pragma once

// Macros
#define NAME value
#define FUNC(x) ((x) * 2)
#undef NAME

// Conditional compilation
#if condition
#elif condition
#else
#endif
#ifdef NAME
#ifndef NAME
#if defined(NAME)

// Predefined macros
__FILE__  __LINE__  __DATE__  __TIME__  __func__  __cplusplus

// Namespaces
namespace Name { }
namespace A::B::C { }  // C++17
using namespace Name;
using Name::symbol;
namespace Alias = Long::Namespace::Name;

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples

# See preprocessor output
g++ -E source.cpp > preprocessed.cpp

# Define macro from command line
g++ -DDEBUG -DMAX_SIZE=200 source.cpp -o output