Importing the Standard Library
User and System Header Units showed importing one standard header, <print>, as a header unit. C++23 goes a step further: the entire standard library is available as a single named module, std (and a POSIX/C-compatibility companion, std.compat).
1 import std;
2
3 auto main() -> int {
4 std::println("no module; global module fragment, no #include <print>");
5 }
There’s no module; global module fragment, no #include <print> — import std; alone brings in std::println and everything else in the standard library namespace. This is genuinely the simplest a translation unit using the standard library can look — but getting there took more sharp edges than anywhere else in this book.
![]() |
CMake’s own documentation calls its |
Enabling import std; in CMake
Two separate opt-ins are required, at two different scopes.
Project-Wide: CMAKE_EXPERIMENTAL_CXX_IMPORT_STD
CMake gates import std; support behind a variable holding a UUID — not a version number, a literal UUID, specific to the CMake build you’re using. It has to be set before project(), because that’s when CMake probes the toolchain’s capability.
1 cmake_minimum_required(VERSION 4.1.2)
2
3 # Must be set before project() -- CMake's `import std;` support detects
4 # toolchain capability at that point. This UUID is specific to this CMake
5 # build (extracted via `strings` on the cmake binary); re-extract if CMake
6 # is ever upgraded. It only enables the CAPABILITY project-wide -- individual
7 # targets still opt in per-target via the CXX_MODULE_STD property.
8 set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
9
10 project(pragmatic_modules LANGUAGES CXX)
That UUID isn’t documented anywhere as a value to type in by hand — it’s meant to be discovered, and it’s tied to the exact CMake build in use. Finding it means searching the cmake binary itself for UUID-shaped strings and testing which one works:
1 strings /usr/bin/cmake | grep -Ei '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
Several UUIDs come back — CMake gates more than one experimental feature this way — so confirm the right one with a minimal smoke test before trusting it project-wide. If CMake is ever upgraded, re-extract; nothing guarantees the value stays the same across releases.
Per-Target: CXX_MODULE_STD
Setting the project-wide variable only makes the capability available. Each individual target that actually uses import std; still has to opt in explicitly:
1 cmake_minimum_required(VERSION 3.30.5)
2
3 project(Manx LANGUAGES CXX)
4 set(module_name manx)
5
6 add_library(${module_name})
7 target_compile_features(${module_name} PUBLIC cxx_std_23)
8 # CMake's synthesized `std` BMI is built with GNU extensions enabled
9 # regardless of the project-wide CMAKE_CXX_EXTENSIONS OFF setting -- any
10 # target that imports std has to match that dialect or the BMI is rejected
11 # as a "configuration mismatch".
12 set_target_properties(${module_name} PROPERTIES
13 CXX_MODULE_STD ON
14 CXX_EXTENSIONS ON
15 )
16 target_sources(${module_name}
17 PUBLIC
18 FILE_SET cxx_modules TYPE CXX_MODULES
19 FILES Manx.cppm
20 )
21
22 add_executable(${PROJECT_NAME} main.cpp)
23 target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
24 # The GNU-extensions dialect tag on manx's BMI (see above) propagates
25 # transitively: any importer of a module that (directly or indirectly)
26 # imports std has to match it too, all the way up the chain.
27 set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS ON)
28
29 target_link_libraries(${PROJECT_NAME} ${module_name})
CXX_MODULE_STD ON (line 13) is the actual per-target opt-in. The CXX_EXTENSIONS ON on both targets (lines 14 and 27) is not optional, and it’s not obvious why it’s needed — it exists to work around the next issue. Every cat chapter that follows and imports std repeats this same pattern.
The Dialect Tag That Follows You Everywhere
CMake builds the std module’s Binary Module Interface once, from a source file the compiler itself ships (bits/std.cc under GCC’s libstdc++, used by both compilers here). Under Clang, that BMI gets compiled with GNU extensions enabled, independent of whatever CMAKE_CXX_EXTENSIONS says for the rest of the project. If a target’s own settings don’t match, the build fails immediately, before a single line of the target’s own module code is even reached:
1 error: GNU extensions was enabled in precompiled file 'CMakeFiles/__cmake_cxx23.dir/std.pcm' but is currently disabled
2 error: precompiled file 'CMakeFiles/__cmake_cxx23.dir/std.pcm' cannot be loaded due to a configuration mismatch with the current compilation [-Wmodule-file-config-mismatch]
Setting CXX_EXTENSIONS ON on the library target fixes that — but the library’s own BMI now also carries the GNU-extensions tag, so the executable target that links it fails the exact same way one step later, complaining about the library’s own .pcm instead of std.pcm. The fix has to be applied at every link in the chain: any target that imports std, directly or by importing something that does, needs CXX_EXTENSIONS ON.
![]() |
GCC doesn’t hit this particular mismatch — targets that import |
Building With Raw Compiler Commands
Both compilers, when using libstdc++, build the std module’s BMI from the same file GCC ships: /usr/include/c++/<version>/bits/std.cc. There’s no portable path to it — it’s tied to the exact GCC install on the machine — but the mechanics of turning it into a BMI mirror the <print> header unit from User and System Header Units closely enough that the same raw-script approach carries over.
With GCC
1 # build the std module's BMI -- GCC resolves it via its implicit
2 # gcm.cache/ layout, same as the <print> header unit, so this has to run
3 # from the same directory everything else is invoked from.
4 g++-15 -std=c++23 -fmodules-ts -c -x c++ /usr/include/c++/15/bits/std.cc -o bin/std.o
5
6 # build the module unit interface - produces a .gcm in gcm.cache
7 g++-15 -std=c++23 -fmodules-ts -c -x c++ Manx.cppm -o bin/Manx.o
8
9 # build main and link with the module
10 g++-15 -std=c++23 -fmodules-ts -c main.cpp -o bin/main.o
11 g++-15 -std=c++23 -fmodules-ts bin/main.o bin/Manx.o bin/std.o -o bin/main
Nothing here is different in kind from building Aegean — the std.cc file is just another module interface unit, compiled first so its BMI exists before anything that imports it.
With Clang
1 # build the std module's BMI -- -x c++-module is required for
2 # -fmodule-output= to take effect; -Wno-reserved-module-identifier
3 # suppresses the (correct, but unwanted here) warning that `std` is a
4 # reserved module name.
5 clang++-22 -std=c++23 -Wno-reserved-module-identifier \
6 -c -x c++-module /usr/include/c++/15/bits/std.cc -fmodule-output=bin/std.pcm -o bin/std.o
7
8 # build the module unit interface - produces a .pcm in bin
9 clang++-22 -std=c++23 -fmodule-file=std=bin/std.pcm \
10 -c Manx.cppm -fmodule-output=bin/Manx.pcm -o bin/Manx.o
11
12 # build main and link with the module
13 clang++-22 -std=c++23 -fmodule-file=std=bin/std.pcm -fmodule-file=Manx=bin/Manx.pcm \
14 -c main.cpp -o bin/main.o
15 clang++-22 -std=c++23 bin/Manx.o bin/std.o bin/main.o -o bin/main
Two flags matter that aren’t needed anywhere else in this book. -x c++-module (line 7) is required — without it, Clang treats std.cc as ordinary C++ source and silently ignores -fmodule-output=, producing no BMI at all despite exiting successfully. -Wno-reserved-module-identifier (line 4) suppresses a warning that would otherwise fire because module names starting with std are reserved for the standard library itself; we’re deliberately building the real thing, so the warning doesn’t apply.
Every subsequent step needs -fmodule-file=std=bin/std.pcm (lines 10-11, 14) so the compiler can find the BMI — Clang has no implicit search path for it the way GCC’s gcm.cache/ convention provides.
Every cat chapter from here through The Plain Case builds exactly this way, changing only the module’s own name.

