Chapter 10: Using C++20 Modules
1. Core Concepts of C++20 Modules
1.1 What Are C++20 Modules?
- Replacement for headers: Modules eliminate textual inclusion via
#include
- Component-based interface: Explicit export/import declarations
- Compilation benefits:
- Avoid repetitive parsing of headers
- Better isolation of implementation details
- Faster compilation times for large projects
1.2 Module Types
- Interface units: Declare exported entities (
export module A;
) - Implementation units: Contain module internals (
module A;
) - Partitions: Split large modules (
export module A:part1;
)
2. CMake Support Matrix
2.1 Version Requirements
- CMake 3.26-3.27: Experimental support via
CXX_MODULES
flag - CMake 3.28+: Native support with automatic module dependency scanning
2.2 Compiler Requirements
- MSVC: Requires Visual Studio 2022 17.1+
- GCC: Needs GCC 11+ with
-fmodules-ts
- Clang: Requires Clang 16+ with
-std=c++20
3. Critical Configuration Steps
3.1 Base Project Setup
cmake_minimum_required(VERSION 3.26)
project(ModernCpp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
3.2 Enabling Module Support
For CMake 3.26-3.27:
set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "3c375311-a3c9-4396-a187-3227ef642046")
set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
For CMake 3.28+:
set(CMAKE_CXX_SCAN_FOR_MODULES ON) # Enable module dependency scanner
4. Module Declaration Patterns
4.1 Interface Unit Example
// math.ixx (MSVC) / math.cppm (GCC/Clang)
export module math;
export int add(int a, int b) {
return a + b;
}
4.2 Consumer Code
import math;
int main() {
return add(2, 3);
}
5. CMake Target Configuration
5.1 Source File Organization
add_executable(app
main.cpp
math.ixx # Module interface file
)
5.2 Modern Target Properties (CMake 3.28+)
target_sources(app
FILE_SET CXX_MODULES
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
FILES math.ixx
)
set_target_properties(app
PROPERTIES
CXX_SCAN_FOR_MODULES ON # Enable module scanning
CXX_MODULE_STD_VERSION 20
)
6. Module Dependency Management
6.1 Implicit Dependencies
- CMake 3.28+ automatically detects
import
statements - Build system ensures correct compilation order
6.2 Explicit Linking (Legacy Systems)
target_link_libraries(app
PRIVATE
$<BUILD_INTERFACE:math> # Module target name
)
7. Common Pitfalls & Solutions
7.1 Module File Extensions
- MSVC:
.ixx
for interface units - GCC/Clang:
.cppm
for interface units - Solution: Use consistent naming convention
7.2 Missing Standard Configuration
# Required in all cases
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
7.3 Scanner Failures
- Symptom: “Missing module interface” errors
- Debugging:
cmake --build . --verbose # See scanner commands
8. Advanced: Module Partitions
8.1 Partition Declaration
// math-impl.cppm
export module math:impl;
export int multiply(int a, int b) {
return a * b;
}
8.2 Main Module Aggregation
// math.cppm
export module math;
export import :impl;
8.3 CMake Configuration
target_sources(math
FILE_SET CXX_MODULES
FILES
math.cppm
math-impl.cppm
)
9. Cross-Compiler Compatibility
9.1 Preprocessor Directives
#if defined(_MSC_VER)
module; // MSVC preamble
#endif
export module platform;
9.2 CMake Abstraction
if(MSVC)
set(MODULE_EXT .ixx)
else()
set(MODULE_EXT .cppm)
endif()
target_sources(app
FILE_SET CXX_MODULES
FILES math${MODULE_EXT}
)
10. Full Example Walkthrough
10.1 Project Structure
project/
├── CMakeLists.txt
├── math.ixx
└── main.cpp
10.2 CMakeLists.txt (CMake 3.28+)
cmake_minimum_required(VERSION 3.28)
project(ModuleDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
add_executable(demo)
target_sources(demo
FILE_SET CXX_MODULES
FILES math.ixx
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
)
target_sources(demo
PRIVATE main.cpp
)
10.3 Build Process
- Configure:
cmake -B build -S .
- Build:
cmake --build build --verbose
- Observe:
- CMake generates
demo.modmap
(module map) - Compiler produces
.pcm
(precompiled module) files
- CMake generates
Key Takeaways
- Version Awareness: Different CMake versions require distinct configuration approaches
- Compiler Flags: Ensure proper
-std
/-fmodules-ts
flags are set - File Organization: Use compiler-specific extensions and clear module boundaries
- Dependency Scanning: Leverage CMake 3.28+ automatic scanning for best results
- Cross-Platform: Abstract compiler differences through CMake variables
Multiple Choice Questions
Question 1
What are the correct ways to enable C++20 module support in CMake for different versions?
A) For CMake 3.26: Set CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API
to a specific hash.
B) For CMake 3.28: Enable CXX_STANDARD 20
and set CXX_SCAN_FOR_MODULES
property.
C) For all versions: Use target_compile_features(my_target PRIVATE cxx_modules)
.
D) For CMake 3.27: Add -DCMAKE_CXX_MODULES=ON
during configuration.
Question 2
Which target properties are critical for correctly handling C++20 modules in CMake?
A) CXX_STANDARD
B) INTERFACE_SOURCES
C) CXX_SCAN_FOR_MODULES
D) LINK_LIBRARIES
Question 3
How should module interface files (.ixx
) be added to a CMake target?
A) Use add_library
with TYPE MODULE
.
B) Use target_sources
with FILE_SET TYPE CXX_MODULES
.
C) Add them via target_include_directories
.
D) Use file(GLOB)
to collect all .ixx
files automatically.
Question 4
Which CMake commands are required to ensure modules are scanned and built correctly?
A) set_property(TARGET my_target PROPERTY CXX_SCAN_FOR_MODULES ON)
B) target_compile_definitions(my_target PRIVATE USE_MODULES)
C) target_link_libraries(my_target PRIVATE module_dependency)
D) target_compile_options(my_target PRIVATE -fmodules-ts)
Question 5
What is the role of the CXX_SCAN_FOR_MODULES
property?
A) Forces CMake to precompile all headers as modules.
B) Enables scanning source files for module dependencies.
C) Automatically generates module interface files.
D) Disables legacy header inclusion for modules.
Question 6
Which compiler flags are necessary for C++20 module support in GCC?
A) -std=c++20
B) -fmodules-ts
C) -fmodule-macros
D) -fprebuilt-modules
Question 7
How are module dependencies declared between targets in CMake?
A) Use target_link_libraries
with PUBLIC
visibility.
B) Use target_include_directories
with module paths.
C) Use target_sources
with FILE_SET
dependencies.
D) Use generator expressions like $<BUILD_INTERFACE:...>
.
Question 8
What is a common mistake when configuring modules in CMake?
A) Forgetting to set CMAKE_CXX_STANDARD
to 20.
B) Using add_executable
instead of add_library
for modules.
C) Placing module interface files in include/
directories.
D) Omitting FILE_SET TYPE CXX_MODULES
in target_sources
.
Question 9
How does CMake handle transitive dependencies for modules?
A) Automatically propagates module dependencies via target_link_libraries
.
B) Requires manual declaration of all dependent modules.
C) Uses INTERFACE
properties to forward dependencies.
D) Only works with STATIC
libraries.
Question 10
Which configuration ensures cross-compiler compatibility for modules?
A) Use #ifdef
guards for compiler-specific module code.
B) Set CMAKE_CXX_COMPILER_FEATURES
to cxx_modules
.
C) Avoid module partitions for compatibility.
D) Conditionally enable compiler flags using $<COMPILE_LANG_AND_ID:CXX,MSVC>
.
Answers and Explanations
Question 1
Correct Answers: A, B
- A) Correct for CMake 3.26: Experimental support requires setting the
CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API
variable to a hash (e.g.,2182bf5c-ef0d-489a-91da-49dbc3095d2a
). - B) Correct for CMake 3.28: Native support requires
CXX_STANDARD 20
and enablingCXX_SCAN_FOR_MODULES
. - C) Incorrect:
cxx_modules
is not a valid feature flag in CMake. - D) Incorrect:
CMAKE_CXX_MODULES
is not a valid CMake variable.
Question 2
Correct Answers: A, C
- A) Correct:
CXX_STANDARD
ensures the compiler uses C++20. - C) Correct:
CXX_SCAN_FOR_MODULES
enables module dependency scanning. - B) Incorrect:
INTERFACE_SOURCES
is unrelated to modules. - D) Incorrect:
LINK_LIBRARIES
handles libraries, not modules.
Question 3
Correct Answer: B
- B) Correct:
FILE_SET TYPE CXX_MODULES
explicitly declares module interface files. - A) Incorrect:
TYPE MODULE
is for shared libraries, not C++20 modules. - C) Incorrect:
target_include_directories
is for headers, not modules. - D) Incorrect:
file(GLOB)
is discouraged for build reliability.
Question 4
Correct Answers: A, D
- A) Correct: Enables scanning for module dependencies.
- D) Correct: GCC requires
-fmodules-ts
for module support. - B) Incorrect:
USE_MODULES
is not a standard definition. - C) Incorrect:
target_link_libraries
handles libraries, not modules.
Question 5
Correct Answer: B
- B) Correct:
CXX_SCAN_FOR_MODULES
triggers CMake to scan sources forimport
statements. - A) Incorrect: Precompiling headers is unrelated.
- C) Incorrect: Interface files are manually added.
- D) Incorrect: Legacy headers are not disabled by this property.
Question 6
Correct Answers: A, B
- A) Correct:
-std=c++20
enables C++20 features. - B) Correct:
-fmodules-ts
enables GCC’s module TS implementation. - C/D) Incorrect: These flags are not valid for GCC modules.
Question 7
Correct Answer: C
- C) Correct:
FILE_SET
dependencies declare module relationships. - A) Incorrect:
target_link_libraries
handles libraries, not module dependencies. - B) Incorrect:
include_directories
does not resolve module imports. - D) Incorrect: Generator expressions are used for conditional logic, not dependencies.
Question 8
Correct Answers: A, D
- A) Correct: Missing
CXX_STANDARD 20
breaks module compilation. - D) Correct: Omitting
FILE_SET
prevents CMake from recognizing modules. - B) Incorrect:
add_executable
can use modules if configured properly. - C) Incorrect:
.ixx
files can reside in any directory.
Question 9
Correct Answer: C
- C) Correct:
INTERFACE
properties propagate dependencies transitively. - A) Incorrect:
target_link_libraries
does not auto-resolve module dependencies. - B) Incorrect: Manual declaration is only for direct dependencies.
- D) Incorrect: Module dependencies work with all library types.
Question 10
Correct Answer: D
- D) Correct: Conditionally applies flags (e.g.,
-fmodules-ts
for GCC,/interface
for MSVC). - A) Incorrect:
#ifdef
guards are not CMake’s responsibility. - B) Incorrect:
cxx_modules
is not a standard feature flag. - C) Incorrect: Partitions are valid but require proper CMake configuration.
Hard-Level Build Exercises for Chapter 10: Using C++20 Modules
Exercise 1: Cross-Version CMake Compatibility for C++ Modules
Scenario:
Your project must support both CMake 3.26 (experimental modules) and CMake 3.28+ (stable modules). Write a CMakeLists.txt
that:
- Detects the CMake version.
- Enables C++20 modules correctly for each version.
- Ensures
mymodule.cppm
(module interface) andclient.cpp
(module consumer) compile without errors.
Key Challenges:
- Conditional logic for CMake versions.
- Handling experimental vs. stable module flags.
- Compiler-specific flags (Clang/GCC/MSVC).
Exercise 2: Multi-Module Dependency Management
Scenario:
A project has three modules:
math.cppm
(declaresadd(int, int)
)advanced.cppm
(importsmath
and declaresmultiply(int, int)
)main.cpp
(importsadvanced
).
Write a CMakeLists.txt
that:
- Declares all modules and their dependencies.
- Ensures correct build order (
math
→advanced
→main
). - Handles module partitions (e.g.,
math-impl.cppm
as a partition ofmath.cppm
).
Key Challenges:
- Explicit declaration of module dependencies.
- Avoiding “missing BMI” errors due to incorrect build order.
- Partition management in CMake.
Exercise 3: Legacy Compiler Flag Injection for Modules
Scenario:
Your team uses a pre-3.28 CMake version but wants to manually inject compiler-specific flags for C++20 modules. Write a script that:
- Checks if the compiler supports modules (Clang >= 15, GCC >= 11, MSVC >= 19.34).
- Adds
-std=c++20 -fmodules-ts
(Clang/GCC) or/std:c++20 /interface
(MSVC) flags. - Validates that
mymodule.cppm
generates a BMI and is consumed byclient.cpp
.
Key Challenges:
- Version detection for compilers.
- Platform-specific flag injection.
- Ensuring BMI generation and consumption.
Solutions & Explanations
Exercise 1 Solution:
cmake_minimum_required(VERSION 3.26)
project(CrossVersionModules)
# Enable C++20 standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(CMAKE_VERSION VERSION_LESS 3.28)
# Experimental module support for CMake <3.28
set(CMAKE_CXX_EXTENSIONS OFF)
add_compile_options(-fmodules-ts) # Clang/GCC
if(MSVC)
add_compile_options(/experimental:module /std:c++latest)
endif()
else()
# Stable module support in CMake >=3.28
cmake_policy(SET CMP0157 NEW) # Enable module awareness
endif()
add_executable(app)
target_sources(app
PRIVATE
client.cpp
mymodule.cppm # Treated as module interface
)
Explanation:
- Version Check:
CMAKE_VERSION
detects if the version is pre-3.28. - Experimental Flags: For older CMake, manual compiler flags (
-fmodules-ts
or/experimental:module
) are injected. - Stable Handling: CMake 3.28+ uses built-in module awareness via
CMP0157
. - Source Declaration:
mymodule.cppm
is marked as a module interface implicitly in CMake 3.28+.
Exercise 2 Solution:
cmake_minimum_required(VERSION 3.28)
project(MultiModule)
set(CMAKE_CXX_STANDARD 20)
cmake_policy(SET CMP0157 NEW) # Enable module processing
add_library(math)
target_sources(math
PUBLIC FILE_SET CXX_MODULES
FILES
math.cppm # Primary module interface
math-impl.cppm # Module partition (impl)
)
add_library(advanced)
target_sources(advanced
PUBLIC FILE_SET CXX_MODULES
FILES advanced.cppm
)
target_link_libraries(advanced PUBLIC math) # Declare dependency
add_executable(app main.cpp)
target_link_libraries(app PRIVATE advanced)
Explanation:
- Module Dependencies:
target_link_libraries
ensuresadvanced
depends onmath
, enforcing build order. - FILE_SET CXX_MODULES: Explicitly marks
math.cppm
andadvanced.cppm
as module interfaces. - Partitions:
math-impl.cppm
is included in themath
target’s sources, treated as a partition.
Exercise 3 Solution:
cmake_minimum_required(VERSION 3.26)
project(LegacyModuleFlags)
# Detect compiler and version
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15)
set(MODULE_FLAGS "-std=c++20 -fmodules-ts -x c++-module")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 11)
set(MODULE_FLAGS "-std=c++20 -fmodules-ts")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.34)
set(MODULE_FLAGS "/std:c++20 /experimental:module /interface")
else()
message(FATAL_ERROR "Compiler does not support C++20 modules!")
endif()
add_executable(app)
target_compile_options(app PRIVATE ${MODULE_FLAGS})
target_sources(app
PRIVATE
client.cpp
mymodule.cppm
)
Explanation:
- Compiler Detection: Checks for Clang/GCC/MSVC and their minimal versions supporting modules.
- Flag Injection: Platform-specific flags are added via
target_compile_options
. - BMI Generation: Flags like
-x c++-module
(Clang) or/interface
(MSVC) ensure BMI files are generated.
Key Takeaways:
- Version-Specific Logic: Use
CMAKE_VERSION
and compiler detection to handle experimental vs. stable features. - Explicit Dependencies:
target_link_libraries
ensures correct build order for modules. - Compiler Flags: Manual injection is needed for pre-3.28 CMake to activate module support.
- Module Interface Declaration: Use
FILE_SET CXX_MODULES
(CMake 3.28+) or implicit detection via file extensions (.cppm
).