C++ Named Modules
beman.big_int can optionally be consumed as the named module beman.big_int,
instead of by including headers:
import beman.big_int;
Module support is inert unless you build it and import it: nothing changes for the header-only consumer, and a header-based program can adopt the module one translation unit at a time — see Limitations.
What the module exports
The module covers the entire public interface that <beman/big_int.hpp>
provides. Importing it brings the following into scope, from the
beman::big_int namespace unless noted otherwise:
-
The class template
basic_big_intand its aliasesbig_int,pmr::basic_big_int, andpmr::big_int, together withuint_multiprecision_tanddiv_result. -
Every namespace-scope operator: the unary operators,
==and<⇒, the arithmetic, bitwise, and shift operators, and the compound assignment operators — including the mixed overloads that take a built-in integer on either side. -
The numeric functions
abs,saturating_cast,in_range,gcd,lcm, andmidpoint. -
swap, both the member and the non-member form. -
div_rem_to_zero(see the division example) andcopy_to_runtime. -
The four user-defined literal suffixes
n,N,_n, and_N, declared inbeman::big_int::literals::big_int_literalsand described in User-Defined Literals. -
The
std::hashspecialization (Hash support), thestd::numeric_limitsspecialization, and thestd::formatterspecialization.
Everything in beman::big_int::detail is compiled into the module but is
not exported: it is reachable from the module’s own implementation but has
no name outside it. See Limitations.
Building the module
The module is off by default. Turn on BEMAN_BIG_INT_BUILD_MODULE (default
OFF) to build it, alongside the ordinary headers and static library:
cmake --preset llvm-release -DBEMAN_BIG_INT_BUILD_MODULE=ON
cmake --build build/llvm-release
or, in one step, use the dedicated workflow preset, which turns the option on and runs the module’s tests:
cmake --workflow --preset llvm-release-module
Building this target needs more from the toolchain than the header build does: CMake 3.30 or later, and a generator that can scan module dependencies for you — Ninja or Visual Studio. Unix Makefiles cannot build it.
|
AppleClang cannot build the module under CMake. CMake’s AppleClang support
never defines the scan-dependencies variables the module build needs, and
cmake-cxxmodules(7) does not list AppleClang among the compilers it supports.
On macOS, either use the ordinary header build — which every other part of
this library supports there — or install Homebrew LLVM and configure with an
|
|
The package installs the interface unit as source ( |
See Limitations for the toolchains this configuration is tested against.
Consuming the module
Bring in the package as usual and link the module target — beman.big_int_module,
aliased beman::big_int_module. Note the underscore: this is not
beman::big_int.module.
find_package(beman.big_int REQUIRED)
add_executable(my_program main.cpp)
target_link_libraries(my_program PRIVATE beman::big_int_module)
|
|
A complete program, computing a product too large for any built-in integer type and inspecting it a few different ways:
import beman.big_int;
#include <array>
#include <format>
#include <iostream>
#include <string_view>
auto main() -> int {
using namespace beman::big_int::literals;
using beman::big_int::big_int;
const big_int a = 123456789_n;
const big_int b = 987654321_n;
const big_int product = a * b;
const big_int mid = midpoint(a, b);
std::array<char, 32> buffer{};
const auto written = to_chars(buffer.data(), buffer.data() + buffer.size(), product);
std::cout << "product: " << std::string_view(buffer.data(), written.ptr) << '\n'
<< "midpoint: " << to_string(mid) << '\n'
<< std::format("hex: {:#x}\n", product);
return 0;
}
a, b, and the literal suffix come from
User-Defined Literals; midpoint from
Numeric Functions; to_chars from
<charconv> Conversions; to_string from
String Conversions; and std::format works because of the
std::formatter specialization. Every one of them is found
the same way whether the program imports the module or includes the headers:
by argument-dependent lookup on the basic_big_int argument, or, for the
formatter, through std::format itself.
The import std variant
The module’s interface unit can be compiled against import std; instead of
the standard library’s headers, behind BEMAN_BIG_INT_USE_STD_MODULE (default
OFF). This is a build-time choice about how the library’s own interface
unit is compiled, not something an importer selects at the point of use:
cmake --preset llvm-release \
-DBEMAN_BIG_INT_BUILD_MODULE=ON \
-DBEMAN_BIG_INT_USE_STD_MODULE=ON \
-DCMAKE_CXX_STDLIB_MODULES_JSON=/path/to/libc++.modules.json
CMake gates import std behind CMAKE_EXPERIMENTAL_CXX_IMPORT_STD, whose
required value changes between CMake releases. You do not need to set it: the
top-level CMakeLists.txt sets it before project() from
infra/cmake/enable-experimental-import-std.cmake, which tracks the value for
each supported CMake version. CMAKE_CXX_STDLIB_MODULES_JSON is needed
only on Clang, and points at the `libc.modules.json` that ships with the
libc you are building against.
Most consumers do not want this option: it changes nothing about the module’s
exported interface, and exists so this library’s own test suite and CI can
exercise import std; end to end. Leave it OFF unless you have a specific
reason to build the interface unit against the standard library module.
Tests
With BEMAN_BIG_INT_BUILD_MODULE=ON and tests enabled, a suite under
tests/beman/big_int/module/ is built and registered with CTest alongside
the rest of the test suite. Those tests consume the library only through
import beman.big_int;, never through the headers, so they exercise the
module boundary itself.
Under BEMAN_BIG_INT_USE_STD_MODULE, only a small GoogleTest-free smoke test
is built. GoogleTest’s own headers are textual, and mixing a textual include
of them with import std; in the same translation unit does not work on
libstdc++ or on the MSVC STL, so the fuller GoogleTest-based module suite is
skipped in that configuration.
Limitations
Macros do not cross the module boundary
None of the library’s function-like configuration macros are visible to code that only imports the module: a macro is a preprocessor-level construct, and importing a module does not textually include anything.
The one public convenience macro is BEMAN_BIG_INT_COPY_TO_RUNTIME. It is
still usable alongside the import, in one of two ways:
-
Include the small, self-contained companion header. It declares nothing and includes nothing else, so it is safe to
#includein the same translation unit asimport beman.big_int;:import beman.big_int; #include <beman/big_int/copy_to_runtime.hpp> constexpr auto packed = BEMAN_BIG_INT_COPY_TO_RUNTIME(beman::big_int::big_int{1} << 128); -
Or spell out what the macro expands to, without including anything:
constexpr auto packed = beman::big_int::copy_to_runtime<decltype([]() { return beman::big_int::big_int{1} << 128; })>();
The automatic configuration macros — BEMAN_BIG_INT_HAS_INT128,
BEMAN_BIG_INT_HAS_BITINT, BEMAN_BIG_INT_WORD_BITS, and
BEMAN_BIG_INT_ALLOW_EXCEPTIONS (or BEMAN_BIG_INT_NO_EXCEPTIONS) — are
likewise unavailable to an importer, as are the library’s internal
diagnostic helpers and BEMAN_BIG_INT_ASSERT. None of these were ever part of
the module’s exported interface; code that depends on one of them needs the
headers, not the import.
User-configurable macros are inputs to the module build
BEMAN_BIG_INT_FORCED_LIMB_WIDTH and BEMAN_BIG_INT_HASH_MAX_OBJECT_WORDS
configure the library where its headers are compiled. For the module, that
point is when the interface unit is built, not when your own translation
unit imports it: by the time you write import beman.big_int;, the headers
are already baked into the module’s binary interface, so defining either
macro in your own translation unit has no effect. Set them when the module
itself is configured and built — for example as compile definitions on the
beman.big_int_module target — not at the point of use.
BEMAN_BIG_INT_SIMD_MUL needs no such care: this project’s CMake applies it
as a PUBLIC compile definition on the beman.big_int target, and the
module target links that target, so turning the option on when the library is
configured reaches every consumer of the module automatically.
detail is not exported
beman::big_int::detail is compiled into the module for the exported names
to use internally, but none of its own names are exported. Code that reaches
into detail — unsupported in the first place — must include the headers;
there is no way to name it through the import.
Do not import and textually include in the same translation unit
A translation unit must consume beman.big_int either by
import beman.big_int; or by including its headers, never both. This is not
merely a style preference: linking the module target defines
BEMAN_BIG_INT_BUILD_MODULE on every translation unit that links it, and
under that macro the library’s public declarations are marked export. An
export declaration outside a module’s purview is ill-formed, so a
translation unit that both imports the module and textually includes, say,
<beman/big_int.hpp>, fails to compile — on every conforming compiler, not
as a quality-of-implementation difference some toolchains happen to tolerate.
What is supported, and is the more useful fact here, is mixing the two
forms across translation units of the same program. The interface unit
wraps its declarations in extern "C++", which attaches them to the global
module rather than to beman.big_int’s own module purview. A `big_int
produced in a translation unit that imported the module and one produced in a
translation unit that only included the headers are therefore the same type,
with the same layout and the same mangled names, and values can be passed
between the two freely. This is what lets an existing header-based program
adopt the module one translation unit at a time, rather than needing an
all-or-nothing conversion.
Tested toolchains
The module is tested on newer toolchains than the header-only library needs:
| Compiler | Version | Standard Library |
|---|---|---|
GCC |
15 or later |
libstdc++ |
Clang |
20 or later |
libc++ |
MSVC |
current |
MSVC STL |
AppleClang is excluded; see Building the module.