You inherit a large C++ codebase built entirely on #include headers and are asked to move it to C++20 modules. The team cannot stop feature work, so the migration must proceed in small steps that keep the project compiling at every commit, and some third-party headers cannot be converted at all. Describe how you sequence the migration, how converted and not-yet-converted code coexist during the transition, and what build-tooling prerequisite must be in place first.
Migrate incrementally, bottom-up: start with leaf libraries that have no outgoing dependencies, wrap legacy headers in the global module fragment, and move up the dependency graph. Use header units for not-yet-converted deps; require a module-aware build system like CMake 3.28+.
- ✗Attempting a single big-bang conversion instead of an incremental, leaf-first one
- ✗Forgetting that
#includeandimportcan coexist during the transition - ✗Ignoring that the build system must be upgraded before any conversion works
- →Why is leaf-first ordering necessary rather than just convenient?
- →How do you keep a header and its module version in sync to avoid ODR issues?
Migration plan
Migrate incrementally, bottom-up through the dependency graph — never a big bang.
Step 1 — leaf dependencies. Start with libraries that have no outgoing dependencies in your code. Converting them breaks nothing below.
// Before: string_utils.h
#pragma once
#include <string>
std::string trim(const std::string& s);
// After: string_utils.cppm
export module string_utils;
import <string>; // header unit for a not-yet-module
export std::string trim(const std::string& s);
Step 2 — legacy headers via the global module fragment. A dependency you cannot touch yet goes in the global fragment — its entities are visible inside the module but never leak to consumers.
module; // global module fragment
#include "legacy_config.h" // not a module yet
export module app_config;
export void load_config(); // legacy_config.h is NOT visible to importers
Step 3 — move up. Once all of a node's dependencies are modules, convert the node itself. #include and import happily coexist in a file during the transition.
Step 4 — the build system. Before any conversion, upgrade the build: CMake 3.28+ with FILE_SET CXX_MODULES, otherwise the "BMI before consumer" ordering cannot be guaranteed.