Modules

Modern C++20 module system for better code organization

📦 What are C++ Modules?

Modules provide a modern alternative to header files, offering better compilation times, stronger encapsulation, and cleaner interfaces while eliminating many preprocessor-related issues.


// math_utils.cppm (module interface)
export module math_utils;

export int add(int a, int b) {
    return a + b;
}
                                    

Key Concepts

📤

export

Make declarations visible

export int function();
📥

import

Use module functionality

import math_utils;
🏗️

Module Interface

Public module declarations

export module my_module;
🔒

Module Implementation

Private module details

module my_module;

🔹 Basic Module Example

C++20 modules replace traditional header files for faster compilation and better encapsulation. A module is declared with export module name; and can export functions, classes, and templates. Importing with import name; gives access only to the exported interface, not implementation details. This reduces compilation times by eliminating redundant parsing of headers and prevents macro pollution. Modules represent a major shift toward more robust, scalable, and efficient C++ code organization.

🔸 Module Interface (math_utils.cppm)

// math_utils.cppm - Module interface unit
export module math_utils;

// Export functions
export int add(int a, int b);
export int multiply(int a, int b);

// Export class
export class Calculator {
public:
    int add(int a, int b);
    int subtract(int a, int b);
private:
    int history_count = 0;  // Not exported
};

// Private helper function (not exported)
int validate_input(int x) {
    return x < 0 ? 0 : x;
}

🔸 Module Implementation (math_utils.cpp)

// math_utils.cpp - Module implementation unit
module math_utils;

// Implement exported functions
int add(int a, int b) {
    return validate_input(a) + validate_input(b);
}

int multiply(int a, int b) {
    return validate_input(a) * validate_input(b);
}

// Implement exported class methods
int Calculator::add(int a, int b) {
    history_count++;
    return a + b;
}

int Calculator::subtract(int a, int b) {
    history_count++;
    return a - b;
}

🔸 Using the Module (main.cpp)

// main.cpp - Using the module
import math_utils;
#include <iostream>

int main() {
    // Use exported functions
    std::cout << "5 + 3 = " << add(5, 3) << std::endl;
    std::cout << "4 * 6 = " << multiply(4, 6) << std::endl;
    
    // Use exported class
    Calculator calc;
    std::cout << "Calculator: 10 + 5 = " << calc.add(10, 5) << std::endl;
    std::cout << "Calculator: 10 - 3 = " << calc.subtract(10, 3) << std::endl;
    
    return 0;
}

Output:

5 + 3 = 8
4 * 6 = 24
Calculator: 10 + 5 = 15
Calculator: 10 - 3 = 7

🔹 Module Partitions

Module partitions organize large modules into logical, manageable subunits. A primary module interface can be split into partition files (e.g., module name:part1;). These partitions are private to the module, allowing internal reorganization without affecting users. This maintains a clean public API while managing complexity. Partitions are essential for large libraries or components, enabling teams to work on different internal sections without creating tight coupling or exposing internal implementation details.

🔸 Primary Module Interface (graphics.cppm)

// graphics.cppm - Primary module interface
export module graphics;

// Export partitions
export import :shapes;
export import :colors;

// Additional exports from primary interface
export void initialize_graphics() {
    // Setup code
}

🔸 Shapes Partition (graphics-shapes.cppm)

// graphics-shapes.cppm - Module partition
export module graphics:shapes;

export class Circle {
public:
    Circle(double radius) : radius_(radius) {}
    double area() const { return 3.14159 * radius_ * radius_; }
    
private:
    double radius_;
};

export class Rectangle {
public:
    Rectangle(double width, double height) 
        : width_(width), height_(height) {}
    double area() const { return width_ * height_; }
    
private:
    double width_, height_;
};

🔸 Colors Partition (graphics-colors.cppm)

// graphics-colors.cppm - Module partition
export module graphics:colors;

export enum class Color {
    Red, Green, Blue, Yellow, Black, White
};

export class ColorPalette {
public:
    void set_primary(Color c) { primary_ = c; }
    Color get_primary() const { return primary_; }
    
private:
    Color primary_ = Color::Black;
};

🔸 Using Partitioned Module

// main.cpp - Using partitioned module
import graphics;  // Imports all partitions
#include <iostream>

int main() {
    initialize_graphics();
    
    Circle circle(5.0);
    Rectangle rect(4.0, 6.0);
    
    std::cout << "Circle area: " << circle.area() << std::endl;
    std::cout << "Rectangle area: " << rect.area() << std::endl;
    
    ColorPalette palette;
    palette.set_primary(Color::Blue);
    
    return 0;
}

Output:

Circle area: 78.5397
Rectangle area: 24

🔹 Modules vs Headers

Modules provide significant advantages over traditional header files in modern C++ development. They eliminate the need for include guards, reducing macro collisions and enforcing clear separation between interface and implementation. Compilation is substantially faster because modules are parsed once into optimized binary form, rather than repeatedly processing headers. Modules also strengthen tooling by providing precise dependency definitions. While headers remain supported for legacy code, adopting export module and import syntax leads to cleaner, more maintainable code, dramatically faster build times, and superior encapsulation—especially beneficial in large-scale projects where compilation overhead becomes critical.

Traditional Headers:

  • Textual inclusion: Code copied into each translation unit
  • Multiple inclusion: Requires include guards
  • Macro pollution: Macros can affect other code
  • Slow compilation: Headers parsed repeatedly

Modern Modules:

  • Semantic import: Pre-compiled module interfaces
  • No redefinition: Modules imported once
  • Macro isolation: Macros don't leak between modules
  • Fast compilation: Modules compiled once, reused

🔸 Header Style (Old)

// utils.h
#ifndef UTILS_H
#define UTILS_H

#define PI 3.14159  // Can pollute other files

int square(int x);

#endif

🔸 Module Style (New)

// utils.cppm
export module utils;

// Macros are module-private by default
#define PI 3.14159

export int square(int x) {
    return x * x;
}

🧠 Test Your Knowledge

What keyword is used to make a declaration visible outside a module?