Skip to content

perf: precompile the implementation into the named module, make C++20 the real floor - #1413

Merged
henryiii merged 8 commits into
mainfrom
henryiii/cxx20-module
Jul 31, 2026
Merged

perf: precompile the implementation into the named module, make C++20 the real floor#1413
henryiii merged 8 commits into
mainfrom
henryiii/cxx20-module

Conversation

@henryiii

@henryiii henryiii commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Close #194.

🤖 AI text below 🤖

Named-module support (import cli11;) already landed in #1286 with CI in #1388, so this PR builds on that work instead of adding a new module. It makes the C++20 floor real, compiles the module in precompiled mode so importers build fast, makes CLI11::Module work everywhere, and documents it all — toward the module ask in #194.

Changes

  • Lower the CLI11_Module compile-feature floor from cxx_std_23 to cxx_std_20. The interface unit only re-exports names from the C++11 headers.
  • Compile the module in precompiled mode: define CLI11_COMPILE on the target (PUBLIC, so importers agree) and build src/Precompile.cpp into the module library. When CLI11_PRECOMPILED is also ON, link the CLI11 static library instead to avoid duplicate implementation objects. Previously the global module fragment held the full header-only library, so every importing TU re-optimized every inline function body; now it holds declarations only.
  • Make CLI11::Module work everywhere without renaming anything: the installed CLI11Config.cmake is now a small wrapper that includes the exported CLI11Targets.cmake and adds CLI11::Module as an alias of CLI11::CLI11_Module (guarded so the config still loads on the 3.14 project minimum without modules), and the build tree gains the matching CLI11::CLI11_Module alias. Both names work for both find_package and add_subdirectory; nothing breaks for 2.7.x consumers.
  • Fail configure with a clear message when CLI11_MODULES is on and CMake < 3.28 (project minimum is 3.14, so the FILE_SET CXX_MODULES error was cryptic).
  • Make tests/module_test compile at C++20 (std::cout instead of std::println, default standard 20) and link the recommended CLI11::Module name.
  • Move the test's includes before import cli11; — GCC 16 rejects textual includes that follow an import whose global module fragment overlaps them (clang accepts either order).
  • Docs: fix the target name in the book (it said CLI11::Modules, which never existed), recommend CLI11::Module, note that macros (CLI11_PARSE) do not come through the import, add a short experimental-modules bullet to the README, and add a complete minimal example (CMakeLists + source) plus an import std; variant on a new book Examples page (book/chapters/modules-example.md) with direct clang/libc++ commands — CMake's own import std support (CMAKE_CXX_MODULE_STD, 3.30+) is still behind the CMAKE_EXPERIMENTAL_CXX_IMPORT_STD gate, so the book does not show it.
  • CI: the clang module install-test job now covers C++20/23/26 and the gcc job enables CLI11_MODULES at C++20/23.

Measured client compile time

Per-TU compile of a 3-option app doing import cli11;, CMake-built Release artifacts, Homebrew clang 22.1.8, C++20, macOS arm64 (best of 3):

Module configuration Client TU
Before (header-only in the GMF) 1.30 s
After (CLI11_COMPILE + precompiled) 0.27 s

For reference, a header-only #include <CLI/CLI.hpp> TU is ~1.9 s and a CLI11_PRECOMPILED header client is ~0.55 s on the same machine, so the module is now the fastest consumption mode. The import std; variant of the book example (C++23, manual clang commands, std::println) builds and runs correctly; its client TU is ~0.5 s, dominated by format machinery in the app itself.

Tested (macOS arm64, CMake 4.4.0, Ninja 1.13.2)

  • Homebrew clang 22.1.8, -std=c++20: CLI11_MODULES=ON build, install, tests/module_test (both tests pass) with CLI11_PRECOMPILED OFF and ON, plus an out-of-tree smoke app that does import cli11; and parses -f/--file and -n/--count (long form, short form, and the required-option error path).
  • Homebrew gcc 16.1.0, -std=c++20: same build/install/module_test cycle passes after the include-order fix; before it, #include <iostream> after the import failed with redefinition errors.
  • Target names: installed package and build tree each consumed through both CLI11::Module and CLI11::CLI11_Module, on both toolchains; a plain non-module install still loads the config, links CLI11::CLI11, and defines no CLI11::Module.
  • import std + import cli11 composed at C++23 with clang/libc++ (manual --precompile of std.cppm and CLI11.cppm, -fmodule-file= for both), exactly the commands now shown in the book.
  • Non-module regression check: full dev preset build, all 24 tests pass.

Known limitations

  • Macros are not exported by modules, so CLI11_PARSE needs a header include or a manual try/catch around app.parse().
  • A TU that imports the module and also includes CLI11 headers must see -DCLI11_COMPILE (propagated automatically through the CMake target); header includes without the definition would create inline duplicates of the compiled implementation.
  • Include-after-import is fragile on GCC in user code; the docs show includes first.
  • CMake import std integration is not wired up (still experimental upstream); the book documents the manual toolchain invocation instead.
  • Not tested here: MSVC, Apple system clang.

Runtime-coverage expansion of the module test is left to #1321.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (92fd7a9) to head (6becebc).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1413   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           19        19           
  Lines         5227      5227           
  Branches      1131      1131           
=========================================
  Hits          5227      5227           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

henryiii added 4 commits July 31, 2026 16:22
Lower the CLI11_Module compile-feature floor from cxx_std_23 to
cxx_std_20; the module interface only re-exports names from the C++11
headers and does not need C++23. Give a clear configure error when
CLI11_MODULES is enabled on CMake older than 3.28 (the project minimum
is 3.14, so the FILE_SET failure was cryptic). Make the module test
compile at C++20 (std::cout instead of std::println) and move its
includes before the import — GCC rejects textual includes that follow
an import whose global module fragment overlaps them.

Verified at C++20 with Homebrew clang 22.1.8 and gcc 16.1.0 (CMake
4.4.0, Ninja).

Assisted-by: ClaudeCode:claude-fable-5
The book named the target CLI11::Modules, which does not exist. The
installed export is CLI11::CLI11_Module (CLI11::Module is a build-tree
alias only). Note that macros such as CLI11_PARSE do not come through
import cli11, and add a short experimental-modules bullet to the README
install section.

Assisted-by: ClaudeCode:claude-fable-5
Define CLI11_COMPILE on CLI11_Module (PUBLIC, so importers see the same
configuration) and compile src/Precompile.cpp into the module library —
or link the CLI11 static library when CLI11_PRECOMPILED is also ON, to
avoid duplicate implementation objects. The global module fragment then
holds declarations only, so an importing TU no longer re-optimizes every
function body: client compile time drops from 1.30 s to 0.27 s per TU
(Homebrew clang 22.1.8, Release, 3-option app, apple-m-series).

Verified at C++20 with Homebrew clang 22.1.8 and gcc 16.1.0: module
build + install + tests/module_test (both precompiled OFF and ON), an
out-of-tree import smoke test, and the full non-module dev test suite.

Assisted-by: ClaudeCode:claude-fable-5
The clang module job now includes C++20, the floor this branch
restores. The gcc job now builds the module itself (CLI11_MODULES=ON)
in addition to the include-based test, at C++20 and C++23.

Assisted-by: ClaudeCode:claude-fable-5
@henryiii henryiii changed the title fix: make C++20 the real floor for the named module perf: precompile the implementation into the named module, make C++20 the real floor Jul 31, 2026
@henryiii
henryiii force-pushed the henryiii/cxx20-module branch from a5fa69d to d3cd1f9 Compare July 31, 2026 20:24
henryiii and others added 4 commits July 31, 2026 16:41
Install a small CLI11Config.cmake wrapper that includes the exported
CLI11Targets.cmake and adds CLI11::Module as an alias of the exported
CLI11::CLI11_Module (guarded on the target existing and CMake >= 3.18,
so the config still loads on the 3.14 project minimum without modules).
Add the matching CLI11::CLI11_Module alias in the build tree so both
names work for add_subdirectory and find_package consumers alike.
CLI11::CLI11_Module stays supported; nothing is renamed. The module
test now links the recommended CLI11::Module name.

Verified with Homebrew clang 22.1.8 and gcc 16.1.0 at C++20: installed
package and build tree, each through both target names, module ctests
green; a plain non-module install still loads the config and links
CLI11::CLI11 on CMake defaults.

Assisted-by: ClaudeCode:claude-fable-5
Show a minimal CMakeLists plus source for import cli11, and a second
variant that also uses import std. CMake support for import std is
still experimental (CMAKE_CXX_MODULE_STD, gated by
CMAKE_EXPERIMENTAL_CXX_IMPORT_STD), so the import std example gives
direct clang/libc++ commands; the flow was verified end to end with
Homebrew clang 22.1.8 and libc++ at C++23.

Assisted-by: ClaudeCode:claude-fable-5
Move the complete import cli11 project and the import std variant from
the installation chapter to a new page, book/chapters/modules-example.md,
listed in the book Examples section and the Doxygen layout. The Modules
section in installation.md keeps the build, install, and link
instructions and links to the new page. Verified with a local Doxygen
build that the page renders and the cross-links resolve both ways.

Assisted-by: ClaudeCode:claude-fable-5
@henryiii
henryiii marked this pull request as ready for review July 31, 2026 20:55
@henryiii
henryiii merged commit 101878a into main Jul 31, 2026
77 checks passed
@henryiii
henryiii deleted the henryiii/cxx20-module branch July 31, 2026 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve compile time

1 participant