Module Linkage (Korat)
The Module Deep Dive chapter described module linkage: a non-exported entity in a module is visible throughout that module’s own translation units but isn’t exported, and — unlike pre-module external linkage — doesn’t collide with an identically named, identically signatured entity declared the same way in a different module. This chapter demonstrates that claim with two independent modules that deliberately share a name.
Module Korat
1 module;
2
3 #include <print>
4
5 export module Korat;
6
7 // Not exported -- has "module linkage": visible throughout Korat's own
8 // units, but distinct from any identically-named, identically-signatured
9 // entity declared the same way in a different module. No `static`, no
10 // anonymous namespace required to keep it from colliding at link time.
11 auto voice() -> char const * {
12 return "แง้ว";
13 }
14
15 export auto meow() -> void {
16 std::println("แมวโคราชพูดว่า \"{}\"", voice());
17 }
voice() (line 8) is a plain, ordinary free function at namespace scope. Before modules, giving two translation units a function this similar — same name, same signature, same return type — with external linkage would be a duplicate-symbol error at link time unless one of them used static or an anonymous namespace. Here, neither is used.
Module KoratEcho
A second, entirely unrelated module reuses the exact same declaration on purpose.
1 module;
2
3 #include <print>
4
5 export module KoratEcho;
6
7 // Same name, same signature as Korat's voice() -- and yet the two never
8 // collide at link time, because each belongs to a different module. Two
9 // ordinary global functions this similar in a pre-modules codebase would
10 // need `static` or an anonymous namespace to avoid a duplicate-symbol error;
11 // modules give every unexported entity that isolation automatically.
12 auto voice() -> char const * {
13 return "(อีกครั้ง) แง้ว";
14 }
15
16 export auto echo() -> void {
17 std::println("KoratEcho says: {}", voice());
18 }
User Code: main.cpp
1 import Korat;
2 import KoratEcho;
3
4 auto main() -> int {
5 meow();
6 echo();
7
8 return 0;
9 }
Both modules are imported (lines 1-2) and both exported functions are called (lines 5-6). Neither voice() is visible here — only meow() and echo(), each module’s exported entry point — but both voice() definitions link successfully into the same executable.
Why This Works
Each compiler mangles a non-exported, module-owned entity’s name with the identity of the module that owns it baked in, even though nothing about voice()’s own declaration says static or names an anonymous namespace. The symbol the linker sees for Korat’s voice() is not the same symbol as KoratEcho’s voice(), despite the source looking identical.
This is a genuinely new form of encapsulation, distinct from the pre-module choices of internal linkage (static / anonymous namespace, visible only within one translation unit) and external linkage (visible and collision-prone everywhere). Module linkage sits between the two: shared across every translation unit of the same module, but private to that module the same way static is private to a single file.
Building With CMake
Korat and KoratEcho are independent library targets, both linked into one executable.
1 cmake_minimum_required(VERSION 3.30.5)
2
3 project(Korat LANGUAGES CXX)
4
5 add_library(korat)
6 target_compile_features(korat PUBLIC cxx_std_23)
7 target_sources(korat
8 PUBLIC
9 FILE_SET cxx_modules TYPE CXX_MODULES
10 FILES Korat.cppm
11 )
12
13 add_library(korat_echo)
14 target_compile_features(korat_echo PUBLIC cxx_std_23)
15 target_sources(korat_echo
16 PUBLIC
17 FILE_SET cxx_modules TYPE CXX_MODULES
18 FILES KoratEcho.cppm
19 )
20
21 add_executable(${PROJECT_NAME} main.cpp)
22 target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
23
24 target_link_libraries(${PROJECT_NAME} korat korat_echo)
Nothing here differs structurally from Aegean’s CMake file except that there are now two separate add_library() module targets instead of one, both listed in the final target_link_libraries() (line 24).