C++ - C++ Modules and Modern Code Organization

C++20 introduced modules, a modern mechanism for organizing and sharing C++ code across different source files. Modules provide an alternative to traditional header files and can reduce some of the problems associated with #include, such as repeated processing, macro interference, and complicated dependencies. (Microsoft Learn)

1. What Are C++ Modules?

In a traditional C++ project, code is commonly divided into header files (.h or .hpp) and implementation files (.cpp).

For example:

// calculator.h
int add(int a, int b);
// calculator.cpp
#include "calculator.h"

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

Another source file can use the function by including the header:

#include "calculator.h"

int main()
{
    int result = add(10, 20);
}

The problem is that #include is essentially a preprocessor operation. The contents of the header are processed repeatedly wherever the header is included. In a large project containing hundreds or thousands of source files, this can contribute significantly to compilation time and dependency complexity.

C++20 modules provide another approach. A module is compiled separately, and other source files can use its exported declarations through import. (Microsoft Learn)


2. Why Were Modules Introduced?

Traditional header files have several limitations.

Repeated processing

Suppose 100 source files include the same large header. The compiler may have to process the header as part of each translation unit.

Modules can be compiled once and their compiled representation reused when imported. This can reduce compilation time, particularly in large projects. (Microsoft Learn)

Macro problems

Macros defined in one part of a program can influence how an included header is processed.

For example:

#define MAX_SIZE 100

#include "library.h"

The contents of library.h may be affected by that macro.

Modules provide stronger isolation because macros and non-exported declarations from a module aren't normally visible to the importing translation unit. (Microsoft Learn)

Dependency management

With traditional headers, changing one commonly used header can cause many source files to be recompiled.

Modules provide a clearer separation between a component's public interface and its internal implementation.


3. Basic Structure of a Module

A simple C++ module can look like this:

// calculator.cpp
export module calculator;

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

The first line:

export module calculator;

declares the module named calculator.

The export keyword indicates that this is the module's interface and that selected declarations can be made available to code that imports the module. (Cppreference)

A program can then import it:

// main.cpp
import calculator;

#include <iostream>

int main()
{
    std::cout << add(10, 20);
}

The important difference is:

#include "calculator.h"

versus:

import calculator;

#include asks the preprocessor to process a header, whereas import makes the exported contents of a compiled module available to the importing translation unit.


4. The export Keyword

The export keyword determines what becomes part of the module's public interface.

Consider:

export module calculator;

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

int subtract(int a, int b)
{
    return a - b;
}

Here, add() is exported:

export int add(int a, int b)

Therefore, code importing the module can use it.

However, subtract() is not exported:

int subtract(int a, int b)

Therefore, it isn't directly visible to code outside the module. (Cppreference)

This creates a useful distinction between public functionality and internal implementation details.


5. The import Keyword

The import keyword is used to consume a module.

For example:

import calculator;

After importing the module, the program can use its exported declarations:

int result = add(5, 10);

The importing source file does not need to know how add() was implemented.

A simplified relationship is:

Module
  |
  |-- Public declarations
  |       |
  |       +---- export
  |
  |-- Private implementation
          |
          +---- not exported

This helps create a cleaner interface between different parts of a large application.


6. Module Interface and Implementation

For larger projects, the module interface and implementation can be separated.

A module interface might contain:

// calculator.ixx
export module calculator;

export int add(int a, int b);
export int multiply(int a, int b);

The implementation can be placed separately:

// calculator.cpp
module calculator;

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

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

The interface tells users what the module provides, while the implementation contains the actual function definitions.

Microsoft's documentation describes module interface units as defining the public interface and module implementation units as providing the implementation. (Microsoft Learn)


7. Module Partitions

Large modules can be divided into smaller logical sections called module partitions.

For example:

export module university:students;

and:

export module university:courses;

A primary module interface can compose these partitions.

This is useful when a module contains many related components.

For example, a university management system might logically contain:

university
    |
    |-- students
    |-- teachers
    |-- courses
    |-- examinations

Instead of placing everything in one enormous module interface, partitions allow the implementation to be organized into manageable units. (Cppreference)


8. Modules vs Header Files

The differences can be summarized as follows:

Feature Header Files Modules
Introduced Traditional C++ mechanism C++20
Main mechanism #include import
Public interface Usually declarations in headers Explicitly exported declarations
Preprocessor involvement Significant Much less for module contents
Macro visibility Can leak across includes Better isolation
Compilation Header processed by importing translation units Module compiled independently
Dependency management Can become complicated Generally cleaner
Code organization Header/implementation model Module interface/implementation model

Modules are not an immediate replacement for every header in every existing project. They can coexist with traditional headers, which makes gradual migration possible. (Microsoft Learn)


9. Modules and Namespaces Are Different

It is important not to confuse modules with namespaces.

A namespace organizes names within the C++ language:

namespace Mathematics
{
    int add(int a, int b);
}

A module organizes the distribution and visibility of program components across translation units.

They can be used together:

export module mathematics;

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

A module therefore deals primarily with code organization and interfaces, while namespaces primarily prevent name collisions and logically group declarations. (Cppreference)


10. Modules and the Preprocessor

One of the important advantages of modules is better separation from preprocessor state.

With traditional headers:

#define DEBUG_MODE

#include "library.h"

the macro can affect how the included header is processed.

With a module:

import library;

the importing source file does not simply receive the module's source text. The module has been compiled separately, and non-exported declarations and module-internal preprocessing details aren't normally exposed to the importer. (Microsoft Learn)

This makes modules less sensitive to the order in which components are included or imported.


11. Standard Library Modules

Modern C++ also provides standardized library modules.

For example, implementations can provide:

import std;

to make the standard library available through the std module where supported.

The C++ standard library has named modules such as std and std.compat in newer standards, with std providing declarations from the standard library. (Cppreference)

However, compiler and standard-library support can vary depending on the compiler version and language standard being used, so developers should check the capabilities of their development environment.


12. Advantages of C++ Modules

The major advantages include:

Faster compilation

Because modules can be compiled independently and their compiled information reused, large projects may experience significant build-time improvements. (Microsoft Learn)

Better encapsulation

Only explicitly exported declarations need to form the public interface.

Reduced macro interference

Module consumers generally don't see the module's internal macros and preprocessing details.

Cleaner dependencies

Instead of depending heavily on chains of included headers, source files can explicitly import the modules they require.

Better project organization

Large applications can be divided into logical modules and partitions.

Easier maintenance

A well-designed module clearly separates what users need from implementation details.


13. Limitations and Considerations

Modules also introduce new concepts that developers must understand.

First, compiler and build-system support needs to be considered. C++20 defines the language feature, but practical module workflows depend on the compiler, standard library, IDE, and build system being used.

Second, existing projects may contain thousands of traditional headers and complicated preprocessor configurations. Converting such projects to modules may require significant restructuring.

Third, modules do not eliminate the need to understand traditional headers. Existing libraries and third-party dependencies may still use them.

Therefore, modules are best viewed as an additional modern C++ mechanism that can gradually complement or replace traditional header-based organization where appropriate. Microsoft specifically notes that modules and headers can be used side by side. (Microsoft Learn)


14. Simple Complete Example

Consider a module that provides a greeting function.

Module interface

// greeting.ixx
export module greeting;

export void sayHello()
{
    std::cout << "Hello from C++ Modules!";
}

A more complete version would import the required standard library functionality:

// greeting.ixx
export module greeting;

import <iostream>;

export void sayHello()
{
    std::cout << "Hello from C++ Modules!";
}

Main program

// main.cpp
import greeting;

int main()
{
    sayHello();
    return 0;
}

The important sequence is:

greeting.ixx
     |
     | export module greeting
     |
     v
Compiled Module
     |
     | import greeting
     v
   main.cpp
     |
     v
  sayHello()

The main.cpp file doesn't need to include a traditional greeting.h header.


15. Module Interface vs Implementation: Key Idea

A useful way to remember the concept is:

Interface = What the module provides

Implementation = How the module provides it

For example:

export int calculate(int a, int b);

tells users that the module provides calculate().

The implementation could be:

int calculate(int a, int b)
{
    return a * b + 10;
}

A user of the module generally doesn't need to know the internal algorithm. This separation makes it easier to change implementation details without changing the public interface.


16. Conclusion

C++20 modules represent a major modernization of C++ code organization. They provide a structured way to share declarations and definitions between translation units without relying entirely on the traditional #include model. The central concepts are module, export, and import. (Cppreference)

Modules can improve compilation efficiency, reduce macro-related problems, provide stronger separation between public and private code, and make large projects easier to organize. They can also be divided into module partitions when a project becomes sufficiently large.

For students, the most important progression to understand is:

Header files
     ↓
#include
     ↓
Traditional code organization
     ↓
C++20 Modules
     ↓
export + module + import
     ↓
Modern code organization

Learning modules is particularly valuable for understanding modern C++20 and later, because they represent one of the language's major steps toward cleaner component-based software development. (Microsoft Learn)