GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing clang.llvm.org — Standard C++ Modules - Clang Standard C++ Modules#Introduction#The term In terms of the C++ Standard, modules consist of two components: “Named Modules” or “Header Units”. This document covers both. Standard C++ Named modules#In order to better understand the compiler’s behavior, it is helpful to understand some terms and definitions for readers who are not familiar with the C++ feature. This document is not a tutorial on C++; it only introduces necessary concepts to better understand use of modules in a project. Other resources at Wikipedia and cppreference can provide more background information about modules if needed. Background and terminology#Module and module unit#A module consists of one or more module units. A module unit is a special kind of translation unit. A module unit should almost always start with a module declaration. The syntax of the module declaration is: [export] module module_name[:partition_name]; Terms enclosed in In this document, module units are classified as:
A primary module interface unit is a module unit whose module declaration is
A module implementation unit is a module unit whose module declaration is
A module partition interface unit is a module unit whose module declaration is
An internal module partition unit is a module unit whose module
declaration is In this document, we use the following terms:
Built Module Interface#A Global module fragment#The How to build projects using modules#Quick Start#Let’s see a “hello world” example that uses modules. // Hello.cppm module; #include <iostream> export module Hello; export void hello() { std::cout << "Hello World!\n"; } // use.cpp import Hello; int main() { hello(); return 0; } Then, on the command line, invoke Clang like: $ clang++ -std=c++20 Hello.cppm --precompile -o Hello.pcm $ clang++ -std=c++20 use.cpp -fmodule-file=Hello=Hello.pcm Hello.pcm -o Hello.out $ ./Hello.out Hello World! In this example, we make and use a simple module A more complex “hello world” example which uses the 4 kinds of module units is: // M.cppm export module M; export import :interface_part; import :impl_part; export void Hello(); // interface_part.cppm export module M:interface_part; export void World(); // impl_part.cppm module; #include <iostream> #include <string> module M:impl_part; import :interface_part; std::string W = "World."; void World() { std::cout << W << std::endl; } // Impl.cpp module; #include <iostream> module M; void Hello() { std::cout << "Hello "; } // User.cpp import M; int main() { Hello(); World(); return 0; } Then, back on the command line, invoke Clang with: # Precompiling the module $ clang++ -std=c++20 interface_part.cppm --precompile -o M-interface_part.pcm $ clang++ -std=c++20 impl_part.cppm --precompile -fprebuilt-module-path=. -o M-impl_part.pcm $ clang++ -std=c++20 M.cppm --precompile -fprebuilt-module-path=. -o M.pcm $ clang++ -std=c++20 Impl.cpp -fprebuilt-module-path=. -c -o Impl.o # Compiling the user $ clang++ -std=c++20 User.cpp -fprebuilt-module-path=. -c -o User.o # Compiling the module and linking it together $ clang++ -std=c++20 M-interface_part.pcm -fprebuilt-module-path=. -c -o M-interface_part.o $ clang++ -std=c++20 M-impl_part.pcm -fprebuilt-module-path=. -c -o M-impl_part.o $ clang++ -std=c++20 M.pcm -fprebuilt-module-path=. -c -o M.o $ clang++ User.o M-interface_part.o M-impl_part.o M.o Impl.o -o a.out We explain the options in the following sections. How to enable standard C++ modules#Standard C++ modules are enabled automatically when the language standard mode
is How to produce a BMI#To generate a BMI for an importable module unit, use either the The The The Generating BMIs with File name requirements#By convention, A BMI should use Clang may fail to build the module if different extensions are used. For
example, if the filename of an // Hello.cpp module; #include <iostream> export module Hello; export void hello() { std::cout << "Hello World!\n"; } // use.cpp import Hello; int main() { hello(); return 0; } In this example, the extension used by the $ clang++ -std=c++20 -x c++-module Hello.cpp --precompile -o Hello.pcm $ clang++ -std=c++20 use.cpp -fprebuilt-module-path=. Hello.pcm -o Hello.out $ ./Hello.out Hello World! Module name requirements#
Therefore, none of the following names are valid by default: std std1 std.foo __test // and so on ... Using a reserved module name is strongly discouraged, but
Specifying BMI dependencies#There are 3 ways to specify a BMI dependency:
The The When these options are specified in the same invocation of the compiler, the
Note: all BMI dependencies must be specified explicitly, either directly or indirectly. See https://github.com/llvm/llvm-project/issues/62707 for details. When compiling a
The $ clang++ -std=c++20 M.cppm --precompile -fmodule-file=M:interface_part=M-interface_part.pcm -fmodule-file=M:impl_part=M-impl_part.pcm -o M.pcm When there are multiple Remember that module units still have an object counterpart to the BMI#While module interfaces resemble traditional header files, they still require compilation. Module units are translation units, and need to be compiled to object files, which then need to be linked together as the following examples show. For example, the traditional compilation processes for headers are like: src1.cpp -+> clang++ src1.cpp --> src1.o ---, hdr1.h --' +-> clang++ src1.o src2.o -> executable hdr2.h --, | src2.cpp -+> clang++ src2.cpp --> src2.o ---' And the compilation processes for module units are like: src1.cpp ----------------------------------------+> clang++ src1.cpp -------> src1.o -,
(header unit) hdr1.h -> clang++ hdr1.h ... -> hdr1.pcm --' +-> clang++ src1.o mod1.o src2.o -> executable
mod1.cppm -> clang++ mod1.cppm ... -> mod1.pcm --,--> clang++ mod1.pcm ... -> mod1.o -+
src2.cpp ----------------------------------------+> clang++ src2.cpp -------> src2.o -'
As the diagrams show, we need to compile the BMI from module units to object files and then link the object files. (However, this cannot be done for the BMI from header units. See the section on header units for more details.) BMIs cannot be shipped in an archive to create a module library. Instead, the
BMIs( clang-cl#
We recommend that build system authors use the above-mentioned Consistency Requirements#Modules can be viewed as a kind of cache to speed up compilation. Thus, like other caching techniques, it is important to maintain cache consistency, which is why Clang does very strict checking for consistency. Options consistency#Compiler options related to the language dialect for a module unit and its non-module-unit uses need to be consistent. Consider the following example: // M.cppm export module M; // Use.cpp import M; $ clang++ -std=c++20 M.cppm --precompile -o M.pcm $ clang++ -std=c++23 Use.cpp -fprebuilt-module-path=. Clang rejects the example due to the inconsistent language standard modes. Not all compiler options are language-dialect options, though. For example: $ clang++ -std=c++20 M.cppm --precompile -o M.pcm # Inconsistent optimization level. $ clang++ -std=c++20 -O3 Use.cpp -fprebuilt-module-path=. # Inconsistent debugging level. $ clang++ -std=c++20 -g Use.cpp -fprebuilt-module-path=. Although the optimization and debugging levels are inconsistent, these compilations are accepted because the compiler options do not impact the language dialect. Note that the compiler currently doesn’t reject inconsistent macro definitions (this may change in the future). For example: $ clang++ -std=c++20 M.cppm --precompile -o M.pcm # Inconsistent optimization level. $ clang++ -std=c++20 -O3 -DNDEBUG Use.cpp -fprebuilt-module-path=. Currently, Clang accepts the above example, though it may produce surprising
results if the debugging code depends on consistent use of Source Files Consistency#Clang may open the input files [1] of a BMI during the compilation. This implies that when Clang consumes a BMI, all the input files need to be present in the original path and with the original contents. To overcome these requirements and simplify cases like distributed builds and sandboxed
builds, users can use the When the Object definition consistency#The C++ language requires that declarations of the same entity in different translation units have the same definition, which is known as the One Definition Rule (ODR). Without modules, the compiler cannot perform strong ODR violation checking because it only sees one translation unit at a time. With the use of modules, the compiler can perform checks for ODR violations across translation units. However, the current ODR checking mechanisms are not perfect. There are a significant number of false positive ODR violation diagnostics, where the compiler incorrectly diagnoses two identical declarations as having different definitions. Further, true positive ODR violations are not always reported. To give a better user experience, improve compilation performance, and for
consistency with MSVC, ODR checking of declarations in the global module
fragment is disabled by default. These checks can be enabled by specifying
Privacy Issue#BMIs are not and should not be treated as an information hiding mechanism. They should always be assumed to contain all the information that was used to create them, in a recoverable form. ABI Impacts#This section describes the new ABI changes brought by modules. Only changes to the Itanium C++ ABI are covered. Name Mangling#The declarations in a module unit which are not in the global module fragment have new linkage names. For example, export module M; namespace NS { export int foo(); } The linkage name of $ llvm-cxxfilt _ZN2NSW1M3fooEv NS::foo@M() The result should be read as The ABI implies that something cannot be declared in a module unit and defined in a non-module unit (or vice-versa), as this would result in linking errors. Despite this, it is possible to implement declarations with a compatible ABI in a module unit by using a language linkage specifier because the declarations in the language linkage specifier are attached to the global module fragment. For example: export module M; namespace NS { export extern "C++" int foo(); } Now the linkage name of Module Initializers#All importable module units are required to emit an initializer function to handle the dynamic initialization of non-inline variables in the module unit. The importable module unit has to emit the initializer even if there is no dynamic initialization; otherwise, the importer may call a nonexistent function. The initializer function emits calls to imported modules first followed by calls to all of the dynamic initializers in the current module unit. Translation units that explicitly or implicitly import a named module must call the initializer functions of the imported named module within the sequence of the dynamic initializers in the translation unit. Initializations of entities at namespace scope are appearance-ordered. This (recursively) extends to imported modules at the point of appearance of the import declaration. If the imported module is known to be empty, the call to its initializer may be omitted. Additionally, if the imported module is known to have already been imported, the call to its initializer may be omitted. Reduced BMI#To support the two-phase compilation model, Clang puts everything needed to produce an object into the BMI. However, other consumers of the BMI generally don’t need that information. This makes the BMI larger and may introduce unnecessary dependencies for the BMI. To mitigate the problem, Clang has a compiler option to reduce the information contained in the BMI. These two formats are known as Full BMI and Reduced BMI, respectively. Users can use the The Note that For the one-phase compilation model (CMake implements this model), with
It is also possible to produce a Reduced BMI with the two-phase compilation
model. When module-unit.cppm --> module-unit.full.pcm -> module-unit.o
|
-> module-unit.reduced.pcm -> consumer1.cpp
-> consumer2.cpp
-> ...
-> consumer_n.cpp
Clang does not emit diagnostics when In a Reduced BMI, Clang does not emit unreachable entities from the global module fragment, or definitions of non-inline functions and non-inline variables. This may not be a transparent change. Consider the following example: // foo.h namespace N { struct X {}; int d(); int e(); inline int f(X, int = d()) { return e(); } int g(X); int h(X); } // M.cppm module; #include "foo.h" export module M; template<typename T> int use_f() { N::X x; // N::X, N, and :: are decl-reachable from use_f return f(x, 123); // N::f is decl-reachable from use_f, // N::e is indirectly decl-reachable from use_f // because it is decl-reachable from N::f, and // N::d is decl-reachable from use_f // because it is decl-reachable from N::f // even though it is not used in this call } template<typename T> int use_g() { N::X x; // N::X, N, and :: are decl-reachable from use_g return g((T(), x)); // N::g is not decl-reachable from use_g } template<typename T> int use_h() { N::X x; // N::X, N, and :: are decl-reachable from use_h return h((T(), x)); // N::h is not decl-reachable from use_h, but // N::h is decl-reachable from use_h<int> } int k = use_h<int>(); // use_h<int> is decl-reachable from k, so // N::h is decl-reachable from k // M-impl.cpp module M; int a = use_f<int>(); // OK int b = use_g<int>(); // error: no viable function for call to g; // g is not decl-reachable from purview of // module M's interface, so is discarded int c = use_h<int>(); // OK In the above example, the function definition of As of Clang 22.x, the Reduced BMI is enabled by default. You may still want to
use Full BMI with Experimental Non-Cascading Changes#This section is primarily for build system vendors. For end compiler users, if you don’t want to read it all, this is helpful to reduce recompilations. We encourage build system vendors and end users to try this out and bring feedback. Before Clang 19, a change in BMI of any (transitive) dependency would cause the outputs of the BMI to change. Starting with Clang 19, changes to non-direct dependencies should not directly affect the output BMI, unless they affect the results of the compilations. We expect that there are many more opportunities for this optimization than we currently have realized and would appreciate feedback about missed optimization opportunities. For example, // m-partA.cppm export module m:partA; // m-partB.cppm export module m:partB; export int getB() { return 44; } // m.cppm export module m; export import :partA; export import :partB; // useBOnly.cppm export module useBOnly; import m; export int B() { return getB(); } // Use.cc import useBOnly; int get() { return B(); } To compile the project (for brevity, some commands are omitted.): $ clang++ -std=c++20 m-partA.cppm --precompile -o m-partA.pcm $ clang++ -std=c++20 m-partB.cppm --precompile -o m-partB.pcm $ clang++ -std=c++20 m.cppm --precompile -o m.pcm -fprebuilt-module-path=. $ clang++ -std=c++20 useBOnly.cppm --precompile -o useBOnly.pcm -fprebuilt-module-path=. $ md5sum useBOnly.pcm 07656bf4a6908626795729295f9608da useBOnly.pcm If the interface of // m-partA.v1.cppm export module m:partA; export int getA() { return 43; } and the BMI for $ clang++ -std=c++20 m-partA.cppm --precompile -o m-partA.pcm $ clang++ -std=c++20 m-partB.cppm --precompile -o m-partB.pcm $ clang++ -std=c++20 m.cppm --precompile -o m.pcm -fprebuilt-module-path=. $ clang++ -std=c++20 useBOnly.cppm --precompile -o useBOnly.pcm -fprebuilt-module-path=. $ md5sum useBOnly.pcm 07656bf4a6908626795729295f9608da useBOnly.pcm then the contents of When What is considered to be a potential contributory BMIs is currently unspecified. However, it is a severe bug for a BMI to remain unchanged following an observable change in the module source files that affects the module consumers. Build systems may utilize this optimization by doing an update-if-changed operation to the BMI that is consumed from the BMI that is output by the compiler. We encourage build systems to add an experimental mode that reuses the cached BMI when direct dependencies did not change, even if transitive dependencies did change. Given that there are potential compiler bugs, we recommend that build systems support this feature as a configurable option so that users can go back to the transitive change mode safely at any time. Interactions with Reduced BMI#With reduced BMI, non-cascading changes can be more powerful. For example, // A.cppm export module A; export int a() { return 44; } // B.cppm export module B; import A; export int b() { return a(); } $ clang++ -std=c++20 A.cppm -c -fmodule-output=A.pcm -fmodules-reduced-bmi -o A.o $ clang++ -std=c++20 B.cppm -c -fmodule-output=B.pcm -fmodules-reduced-bmi -o B.o -fmodule-file=A=A.pcm $ md5sum B.pcm 6c2bd452ca32ab418bf35cd141b060b9 B.pcm And let’s change the implementation for export module A; int a_impl() { return 99; } export int a() { return a_impl(); } and recompile the example: $ clang++ -std=c++20 A.cppm -c -fmodule-output=A.pcm -fmodules-reduced-bmi -o A.o $ clang++ -std=c++20 B.cppm -c -fmodule-output=B.pcm -fmodules-reduced-bmi -o B.o -fmodule-file=A=A.pcm $ md5sum B.pcm 6c2bd452ca32ab418bf35cd141b060b9 B.pcm We should find the contents of This only happens with a reduced BMI. With reduced BMIs, we won’t record the function body
of Performance Tips#Reduce duplications#While it is valid to have duplicated declarations in the global module fragments of different module units, it is not free for Clang to deal with the duplicated declarations. A translation unit will compile more slowly if there are a lot of duplicated declarations between the translation unit and modules it imports. For example: // M-partA.cppm module; #include "big.header.h" export module M:partA; ... // M-partB.cppm module; #include "big.header.h" export module M:partB; ... // other partitions ... // M-partZ.cppm module; #include "big.header.h" export module M:partZ; ... // M.cppm export module M; export import :partA; export import :partB; ... export import :partZ; // use.cpp import M; ... // use declarations from module M. When module; #include "big.header.h" export module m:big.header.wrapper; export ... // export the needed declarations // M-partA.cppm export module M:partA; import :big.header.wrapper; ... // M-partB.cppm export module M:partB; import :big.header.wrapper; ... // other partitions ... // M-partZ.cppm export module M:partZ; import :big.header.wrapper; ... // M.cppm export module M; export import :partA; export import :partB; ... export import :partZ; // use.cpp import M; ... // use declarations from module M. Reducing the duplication from textual includes is what improves compile-time performance. To help users to identify such issues, we add a warning Transitioning to modules#It is best for new code and libraries to use modules from the start if possible. However, it may be a breaking change for existing code or libraries to switch to modules. As a result, many existing libraries need to provide both headers and module interfaces for a while to not break existing users. This section provides some suggestions on how to ease the transition process for existing libraries. Note that this information is only intended as guidance, rather than as requirements to use modules in Clang. It presumes the project is starting with no module-based dependencies. ABI non-breaking styles#export-using style#module; #include "header_1.h" #include "header_2.h" ... #include "header_n.h" export module your_library; export namespace your_namespace { using decl_1; using decl_2; ... using decl_n; } This example shows how to include all the headers containing declarations which
need to be exported, and uses export extern-C++ style#module; #include "third_party/A/headers.h" #include "third_party/B/headers.h" ... #include "third_party/Z/headers.h" export module your_library; #define IN_MODULE_INTERFACE extern "C++" { #include "header_1.h" #include "header_2.h" ... #include "header_n.h" } Headers (from #ifdef IN_MODULE_INTERFACE #define EXPORT export #else #define EXPORT #endif and put Also, it is recommended to refactor headers to include third-party headers conditionally: #ifndef IN_MODULE_INTERFACE #include "third_party/A/headers.h" #endif #include "header_x.h" ... This can be helpful because it gives better diagnostic messages if the module interface unit is not properly updated when modifying code. This approach works because the declarations with language linkage are attached to the global module. Thus, the ABI of the modular form of the library does not change. While this style is more involved than the export-using style, it makes it easier to further refactor the library to other styles. ABI breaking style#The term The pattern for ABI breaking style is similar to the export extern-C++ style. module; #include "third_party/A/headers.h" #include "third_party/B/headers.h" ... #include "third_party/Z/headers.h" export module your_library; #define IN_MODULE_INTERFACE #include "header_1.h" #include "header_2.h" ... #include "header_n.h" #if the number of .cpp files in your project are small module :private; #include "source_1.cpp" #include "source_2.cpp" ... #include "source_n.cpp" #else // the number of .cpp files in your project are a lot // Using all the declarations from third-party libraries which are // used in the .cpp files. namespace third_party_namespace { using third_party_decl_used_in_cpp_1; using third_party_decl_used_in_cpp_2; ... using third_party_decl_used_in_cpp_n; } #endif (And add The ABI with modules is different and thus we need to compile the source files into the new ABI. This is done by an additional part of the interface unit: #if the number of .cpp files in your project are small module :private; #include "source_1.cpp" #include "source_2.cpp" ... #include "source_n.cpp" #else // the number of .cpp files in your project are a lot // Using all the declarations from third-party libraries which are // used in the .cpp files. namespace third_party_namespace { using third_party_decl_used_in_cpp_1; using third_party_decl_used_in_cpp_2; ... using third_party_decl_used_in_cpp_n; } #endif If the number of source files is small, everything can be put in the private module fragment directly (it is recommended to add conditional includes to the source files as well). However, compile time performance will be bad if there are a lot of source files to compile. Note that the private module fragment can only be in the primary module interface unit and the primary module interface unit containing the private module fragment should be the only module unit of the corresponding module. In this case, source files (.cpp files) must be converted to module implementation units: #ifndef IN_MODULE_INTERFACE // List all the includes here. #include "third_party/A/headers.h" ... #include "header.h" #endif module your_library; // Following off should be unchanged. ... The module implementation unit will import the primary module implicitly. Do not include any headers in the module implementation units as it avoids duplicated declarations between translation units. This is why non-exported using declarations should be added from third-party libraries in the primary module interface unit. If the library is provided as What if there are headers only included by the source files#The above practice may be problematic if there are headers only included by the source files. When using a private module fragment, this issue may be solved by including those headers in the private module fragment. While it is OK to solve it by including the implementation headers in the module purview when using implementation module units, it may be suboptimal because the primary module interface units now contain entities that do not belong to the interface. This can potentially be improved by introducing a module partition implementation unit. An internal module partition unit is an importable module unit which is internal to the module itself. The ABI of your library#You can skip this section your library doesn’t ship ABI. With the ABI breaking style, for every ABI you shipped in your library, you should provide a corresponding ABI within the modules version. For example, if this your library before provding modules, // header.h #pragma once #include <cstdint> namespace example { class C { public: std::size_t inline_get() { return 42; } std::size_t get(); }; } // src.cpp #include "header.h" std::size_t example::C::get() { return 43 + inline_get(); } Then you will ship ABI may be like: $nm -ACD libexample.so libexample.so:0000000000001130 W example::C::inline_get() libexample.so:0000000000001110 T example::C::get() Then with ABI breaking style, your code may look like: // example.cppm export module example; import std; // and other third-party modules, if any #define IN_MODULE_WRAPPER #include "header.h" // omit changing of header.h for brevity // src.module.cpp module example; #define IN_MODULE_IMPL #include "src.cpp" // omit changing of src.cpp for brevity And your ABI should look like: $llvm-nm -ACD libexample.so libexample.so: 0000000000001060 T initializer for module example libexample.so: 0000000000001150 W example::C::inline_get() libexample.so: 0000000000001130 T example::C::get() libexample.so: 0000000000001180 T example::C@example::inline_get() libexample.so: 0000000000001160 T example::C@example::get() Here Which part of the ABI will be broken?#While your library keeps ABI compatible by providing both ABI versions. The users’s ABI may be breaking if they used the ABI of modules’ version. This is similar with the famous ABI break in GCC 5’s libstdc++ for C++11. Although your library remains compatible with both ABIs, for your library’s users, choosing the module-based ABI will also break their ABI. For example, if your user’s code contains: #include "header.h" namespace user { void user_def(example::C& c) { } } then, (if your user will ship ABI too), their shipped ABI may look like: $ llvm-nm -ACD libuser.so libuser.so: 0000000000001100 T user::user_def(example::C&) But when your user switches to your ABI-breaking style module: import example; namespace user { void user_def(example::C& c) { } } The corresponding ABI may look like: $ llvm-nm -ACD libuser.so libuser.so: 0000000000001100 T user::user_def(example::C@example&) Here we can find the ABI break from Less duplicated generated code#Another benefit of ABI breaking style is, it is more likely to modules native style. So that compiler can generate the previously implicitly inline entities (e.g., virtual tables, variables and functions) to the module unit instead of generating them in every translation unit used. e.g, for the above example, in header version, the definition of
This is helpful for the whole build process to generate less duplicated code, which will results in smaller binary size and faster compilation speed. Note that for compatibility, the explicitly inline entities will still be inline in modules. So that if you have explicitly inline entities and you’re using ABI breaking style, it is suggestted to use a macro to control inliness of the entity. e.g., // your_header.h #include <cstdint> inline void your_interface() {} within ABI breaking style, we suggest, // your_header.h #ifndef IN_MODULE_WRAPPER #include <cstdint> #endif #ifdef IN_MODULE_WRAPPER #define MY_EXPORT export #else #define MY_EXPORT #endif #ifdef IN_MODULE_WRAPPER #define MY_INLINE #else #define MY_INLINE inline #endif MY_EXPORT MY_INLINE void your_interface() {} // your_module_interface.cppm export module your_library; import std; #define IN_MODULE_WRAPPER #include "your_header.h" So that Providing a header to skip parsing redundant headers#Many redeclarations shared between translation units cause Clang to have slower compile-time performance. Further, there are known issues with include after import. Even when that issue is resolved, users may still get slower compilation speed and larger BMIs. For these reasons, it is recommended to not include headers after importing the corresponding module. However, it is not always easy if the library is included by other dependencies, as in: #include "third_party/A.h" // #include "your_library/a_header.h" import your_library; or import your_library; #include "third_party/A.h" // #include "your_library/a_header.h" For such cases, it is best if the library providing both module and header interfaces also provides a header which skips parsing so that the library can be imported with the following approach that skips redundant redeclarations: import your_library; #include "your_library_imported.h" #include "third_party/A.h" // #include "your_library/a_header.h" but got skipped The implementation of #pragma once #ifndef YOUR_LIBRARY_IMPORTED ... #endif If the modules imported by the library provide such headers, remember to add
them to Importing modules#When there are library dependencies providing modules, the module dependencies
should be imported in your module as well. Many existing libraries will fall
into this category once the All library dependencies providing modules#Of course, most of the complexity disappears if all the library dependencies provide modules. Headers need to be converted to include third-party headers conditionally. Then, for the export-using style: module; import modules_from_third_party; #define IN_MODULE_INTERFACE #include "header_1.h" #include "header_2.h" ... #include "header_n.h" export module your_library; export namespace your_namespace { using decl_1; using decl_2; ... using decl_n; } or, for the export extern-C++ style: export module your_library; import modules_from_third_party; #define IN_MODULE_INTERFACE extern "C++" { #include "header_1.h" #include "header_2.h" ... #include "header_n.h" } or, for the ABI-breaking style, export module your_library; import modules_from_third_party; #define IN_MODULE_INTERFACE #include "header_1.h" #include "header_2.h" ... #include "header_n.h" #if the number of .cpp files in your project are small module :private; #include "source_1.cpp" #include "source_2.cpp" ... #include "source_n.cpp" #endif Non-exported Partial library dependencies providing modules#If the library has to mix the use of Reachability of internal partition units#The internal partition units are sometimes called implementation partition units in other documentation. However, the name may be confusing since implementation partition units are not implementation units. According to [module.reach]p1 and [module.reach]p2 (from N4986):
For example, // a.cpp import B; int main() { g<void>(); } // b.cppm export module B; import :C; export template <typename T> inline void g() noexcept { return f<T>(); } // c.cppm module B:C; template<typename> inline void f() noexcept {} The internal partition unit The suggested approach for using an internal partition unit in Clang is to only import them in the implementation unit. Using Clang Module Map to Avoid mixing #include and import problems#Note Discussion in this section is experimental. Problems Background#As discussed before, the redeclaration in different TU is one of the major problems of using modules from the perspective of the compiler. The redeclaration pattern is a major trigger of compiler bugs. And even if the compiler accepts the redeclaration pattern as expected, the compilation performance will be affected too. e.g, // a.h #pragma once class A { ... }; // a.cppm module; #include "a.h" export module a; export using ::A; // a.cc import a; #include "a.h" A a; Here in To avoid the redeclaration pattern, in previous section, we suggested users to comment out thirdparty headers manually. And here we will introduce another approach to avoid such redeclaration pattern by using clang module map. Clang Module Map Background#Clang Module Map is a feature of Clang Header Modules. See Clang Module for full introduction of Clang Header Modules. Here we would only introduce Clang Header Modules to make this document self contained. Clang Implicit Header Modules#In Clang Implicit Header Module mode, Clang will read the module map and compile the header in the module map into a module file and use the module file automatically. This sounds very nice. But due to the complexity, this is not so wonderful in practice. Clang has to compile the same header in different preprocessor context into different module file for correctness conservatively. Then this may trigger the redeclaration in different TU problems. So that the user of implicit header modules has to design a module system bottom up carefully. And clang implicit header module has many issues with soundness and performance due to tradeoffs made for module reuse and filesystem contention. Clang Explicit Header Modules#Clang explicit header modules offloads the job of creating and managing module files to the build system. Given the C++20 modules and clang header modules actually share the same underlying implementation, it is actually possible to reuse the interface of clang module map for C++20 named modules. Technically, Clang Explicit Header Modules may be able to solve the redeclaration problem. For the above example, e.g, // a.h #pragma once class A { ... }; // a.cppm module; #include "a.h" export module a; export using ::A; // a.cc import a; #include "a.h" A a; The build system can build the header into a module file and use it in both But there are problems: (1) the build system needs to support clang explicit module. (2) The interaction between clang named modules and clang header modules are theoriticall fine but not verified in practice. And also the document itself is about standard C++ modules, so we won’t expand here. Examples#To use Clang Module Map for C++20 Named Modules, end users have to wait for the support from build systems. Here we ignore the build systems to help users to understand the mechanism. Here is an example of using clang module map to replace a header to an import of a module. // a.h #pragma once static_assert(false, "don't include a.h"); // main.cpp #include "a.h" int main() { return 0; } // a.cppm module; #include <iostream> export module a; struct Init { Init() { std::cout << "Module 'a' got imported" << std::endl; } }; Init a; // a.cppm.modulemap module a { header "a.h" } Then invoke Clang with: $ clang++ -std=c++20 a.cppm -c -fmodule-output=a.pcm -o a.o $ clang++ -std=c++20 main.cpp -fmodule-map-file=a.cppm.modulemap -fmodule-file=a=a.pcm a.o -o main $ ./main Module 'a' got imported We can find that the header file The secret comes from the flag Then we can try to use the mechanism to avoid redeclaration pattern for header wrapping modules. // a.h #pragma once class A { ... }; // a.cppm module; #include "a.h" export module a; export using ::A; // a.cc import a; #include "a.h" A a; // a.cppm.modulemap module a { header "a.h" } Similarly, when we compile An imaginable problem with this approach maybe the hidden inclusion. e.g, // b.h #pragma once struct B {}; // a.h #pragma once #include "b.h" struct A { B b; }; // b.cppm export module b; export extern "C++" struct B { }; // a.cppm export module a; import b; export extern "C++" struct A { B b; }; // test.cc import a; #include "a.h" A a; B b; // a.cppm.modulemap module a { header "a.h" } // b.cppm.modulemap module b { header "b.h" } The example is valid if we don’t use the module map: $ clang++ -std=c++20 b.cppm -c -fmodule-output=b.pcm -o b.o $ clang++ -std=c++20 a.cppm -c -fmodule-output=a.pcm -fmodule-file=b=b.pcm -o a.o $ clang++ -std=c++20 test.cc -fmodule-file=a=a.pcm -fmodule-file=b=b.pcm -fsyntax-only But if we enable the module map, the example is invalid: $ clang++ -std=c++20 test.cc -fmodule-map-file=a.cppm.modulemap -fmodule-file=a=a.pcm -fmodule-map-file=b.cppm.modulemap -fmodule-file=b=b.pcm -fsyntax-only test.cc:4:1: error: declaration of 'B' must be imported from module 'b' before it is required 4 | B b; | ^ b.cppm:2:28: note: declaration here is not visible 2 | export extern "C++" struct B { }; | ^ 1 error generated. A suggested convention for end users and build systems#As said, the build system is a vital role in this strategy. However, for build systems, it is not easy to support clang explicit header modules or support the module map with C++20 named modules generally. The complexity for build system won’t be less than supporting C++20 named modules. So here we suggest a convention between end users and build systems to ease the implementation burden of build systems and help end users to avoid the redeclaration problem from mixing #include and import. For end users who is the author of header based library offering named module wrappers, The header’s interface should be a subset of the module interface excluding user-facing macros.
The number of the module map may not be a lot sicne this is still a header based library. For build systems,
The point of the approach is, the build system can reuse the result of C++20 named modules to manage depencies. So that the implementation burden of build systems is largely reduced. Tricks for using std modules without changing your code#This section introduces two methods to use std modules in a project without changing (too many) the code. It is still better to refactor the code explicitly. The methods introduced in this section needs build system’s support to run stably. But even without build system’s support, end users can make this experiment to have a feeling about the benefits of introduce std module to their projects. Preload the std module#We can assume almost all C++ TU depends on the STL. So we can assume
it is safe to import std in almost all C++ TUs. And clang have a mechanism
to load the corresponding module eagerly: So if we built the std module’s BMI ahead of time, we can pass
// hello.cpp #include <string> #include <iostream> int main() { std::string hello = "Hello World.\n"; std::cout << hello << std::endl; return 0; } will be effectively equal to: import std; #include <string> #include <iostream> int main() { std::string hello = "Hello World.\n"; std::cout << hello << std::endl; return 0; } Let’s take a quick measure (the numbers may vary in different environments): $ time clang++ -std=c++23 hello.cpp -o hello real 0m1.245s user 0m1.154s sys 0m0.085s and let’s build the std module first and preload it without changing hello.cpp. $ clang++ -std=c++23 <path-to-std-module>/std.cppm -c -o std.o -fmodule-output=std.pcm $ time clang++ -std=c++23 hello.cpp -o hello -fmodule-file=std.pcm real 0m0.795s user 0m0.707s sys 0m0.084s we should be able to observe a clear compilation reduction. Clang has some optimizations for the case of import-before-include but also the pattern import-before-include may trigger compiler internal issues. But bugs are bugs. They should be reported and fixed. However, it is straight-forward that import std; #include <string> #include <iostream> will be slower to be compiled than import std; And due to compiler internal data structures, the mixed form will be much more slower than the pure import style due to the same declration apears in different TU issues (see above sections for more discussions). Use Module Map to replace includes to imports#So is there any tricks we can use to replace the includes with the imports without changing the source code? Yes, clang has a module map. Although clang’s module map was invented for clang header module maps, it can be used with named modules as well. Let’s start with a simple example: // std.modulemap module std [system] { requires cplusplus header "algorithm" header "bitset" header "complex" header "deque" header "exception" header "fstream" header "functional" header "iomanip" header "ios" header "iosfwd" header "iostream" header "istream" header "iterator" header "limits" header "list" header "locale" header "map" header "memory" header "new" header "numeric" header "ostream" header "queue" header "set" header "sstream" header "stack" header "stdexcept" header "streambuf" header "string" header "typeinfo" header "utility" header "valarray" header "vector" header "array" header "atomic" header "chrono" header "codecvt" header "condition_variable" header "forward_list" header "future" header "initializer_list" header "mutex" header "ratio" header "regex" header "scoped_allocator" header "system_error" header "thread" header "tuple" header "typeindex" header "unordered_map" header "unordered_set" header "optional" header "any" header "variant" } the paths of the files referenced in module map is relative to the module map. So they can’t be associated with the system headers. There are two solutions for this. First, we can create the softlinks for the headers: mkdir -p std-module-map cp std.modulemap std-module-map/. cd std-module-map libcxx_dir=... awk -F '"' '/^[[:space:]]*header[[:space:]]+"/ {print $2}' std.modulemap | while IFS= read -r header; do ln -s "$libcxx_dir/$header" "$header" done then $ time bin/clang++ -std=c++23 hello.cpp std.o -o hello $LIBCXX_FLAGS -fmodule-map-file=std-module-map/std.modulemap -fmodule-file=std=std.pcm -Rmodule-import
hello.cpp:1:2: remark: importing module 'std' from 'std.pcm' [-Rmodule-import]
1 | #include <string>
| ^
real 0m0.171s
user 0m0.128s
sys 0m0.043s
We can see the compilation time is highly reduced! The flag The other solution to associate the std headers with the module map is to use clang’s VFS system. // vfs.yaml { 'version': 0, 'overlay-relative': True, 'roots': [ { 'name': '<path-to-std-headers>', 'type': 'directory', 'contents': [ { 'name': 'std.modulemap', 'type': 'file', 'external-contents': 'std.modulemap' }, ] } ] } then $ time clang++ -std=c++23 hello.cpp std.o -o hello -fmodule-map-file=<path-to-std-headers>/std.modulemap -fmodule-file=std=std.pcm -ivfsoverlay vfs.yaml -Rmodule-import
hello.cpp:1:2: remark: importing module 'std' from 'std.pcm' [-Rmodule-import]
1 | #include <string>
| ^
real 0m0.172s
user 0m0.122s
sys 0m0.049s
the vfs.yaml means to place the std.modulemap file to the directory of std headers virtually. Why not use clang header modules?#Clang header modules have two modes:
Clang implicit header modules sounds nice but it is prety fragile. Users need to be careful to manage the headers and builds to make it effective. Clang explicit header modules are much better. This is the way the inventors of clang header modules are using. But they need supports from build systems. Extends to other modules#Extends this to other modules has some problems as:
These problems are solvable if we introduce new features. This may be possible if the community has more interests. What build systems need to do#Except automating the steps I described above, an important step to manage the flags the consumer used. E.g., if they are two TUs, one is using C++20 and the other is using C++23, the build system needs to build two BMIs of the std module so that the consumers can consume successfully. Summary#This section introduces two tricks to use std modules without changing the source code. These tricks needs support from build systems to make them smoothy in real cases. The preload tricks will load the std module to all the TUs. It is relatively slower than a clean rewrite (replace #include to imports). And it may trigger more compiler internal bugs. But it should be faster than the pure includes. The module map solution can make the compilation much more faster. But it needs more steps. Known Issues#The following describes issues in the current implementation of modules. Please
see
the issues list for modules
for a list of issues or to file a new issue if you don’t find an existing one.
When creating a new issue for standard C++ modules, please start the title with
A high-level overview of support for standards features, including modules, can be found on the C++ Feature Status page. Including headers after import is not well-supported#The following example is accepted: #include <iostream> import foo; // assume module 'foo' contain the declarations from `<iostream>` int main(int argc, char *argv[]) { std::cout << "Test\n"; return 0; } but if the order of import foo; // assume module 'foo' contain the declarations from `<iostream>` #include <iostream> int main(int argc, char *argv[]) { std::cout << "Test\n"; return 0; } Both of the above examples should be accepted. This is a limitation of the implementation. In the first example, the compiler
will see and parse Ignored
|
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |