Docs

Preprocessor Namespaces

Preprocessor Directives & Namespaces in C++

Table of Contents

  1. What is the Preprocessor
  2. Include Directives
  3. Macros
  4. Conditional Compilation
  5. Pragmas
  6. Namespaces
  7. Best Practices

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

Best Practices

✅ 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

Quick Reference

// 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
Preprocessor Namespaces - C++ Tutorial | DeepML