perf: precompile the implementation into the named module, make C++20 the real floor - #1413
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
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
force-pushed
the
henryiii/cxx20-module
branch
from
July 31, 2026 20:24
a5fa69d to
d3cd1f9
Compare
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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, makesCLI11::Modulework everywhere, and documents it all — toward the module ask in #194.Changes
CLI11_Modulecompile-feature floor fromcxx_std_23tocxx_std_20. The interface unit only re-exports names from the C++11 headers.CLI11_COMPILEon the target (PUBLIC, so importers agree) and buildsrc/Precompile.cppinto the module library. WhenCLI11_PRECOMPILEDis also ON, link theCLI11static 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.CLI11::Modulework everywhere without renaming anything: the installedCLI11Config.cmakeis now a small wrapper that includes the exportedCLI11Targets.cmakeand addsCLI11::Moduleas an alias ofCLI11::CLI11_Module(guarded so the config still loads on the 3.14 project minimum without modules), and the build tree gains the matchingCLI11::CLI11_Modulealias. Both names work for bothfind_packageandadd_subdirectory; nothing breaks for 2.7.x consumers.CLI11_MODULESis on and CMake < 3.28 (project minimum is 3.14, so theFILE_SET CXX_MODULESerror was cryptic).tests/module_testcompile at C++20 (std::coutinstead ofstd::println, default standard 20) and link the recommendedCLI11::Modulename.import cli11;— GCC 16 rejects textual includes that follow an import whose global module fragment overlaps them (clang accepts either order).CLI11::Modules, which never existed), recommendCLI11::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 animport std;variant on a new book Examples page (book/chapters/modules-example.md) with direct clang/libc++ commands — CMake's ownimport stdsupport (CMAKE_CXX_MODULE_STD, 3.30+) is still behind theCMAKE_EXPERIMENTAL_CXX_IMPORT_STDgate, so the book does not show it.CLI11_MODULESat 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):CLI11_COMPILE+ precompiled)For reference, a header-only
#include <CLI/CLI.hpp>TU is ~1.9 s and aCLI11_PRECOMPILEDheader client is ~0.55 s on the same machine, so the module is now the fastest consumption mode. Theimport 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)
-std=c++20:CLI11_MODULES=ONbuild, install,tests/module_test(both tests pass) withCLI11_PRECOMPILEDOFF and ON, plus an out-of-tree smoke app that doesimport cli11;and parses-f/--fileand-n/--count(long form, short form, and the required-option error path).-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.CLI11::ModuleandCLI11::CLI11_Module, on both toolchains; a plain non-module install still loads the config, linksCLI11::CLI11, and defines noCLI11::Module.import std+import cli11composed at C++23 with clang/libc++ (manual--precompileofstd.cppmandCLI11.cppm,-fmodule-file=for both), exactly the commands now shown in the book.devpreset build, all 24 tests pass.Known limitations
CLI11_PARSEneeds a header include or a manual try/catch aroundapp.parse().-DCLI11_COMPILE(propagated automatically through the CMake target); header includes without the definition would create inline duplicates of the compiled implementation.import stdintegration is not wired up (still experimental upstream); the book documents the manual toolchain invocation instead.Runtime-coverage expansion of the module test is left to #1321.