Since the introduction of header files in theC programming languageheader files have been the main tool for declaring elements that are notdefined but are used in source files. E.g., to useprintf inmainthe preprocessor directive#include <stdio.h> had to be specified.
Header files are still extensively used inC++, but gradually somedrawbacks emerged. One minor drawback is that, inC++, header filesfrequently not merely contain function and variable declarations but oftenalso type definitions (like class interfaces and enum definitions). Whendesigning header files the software engineer therefore commonly distinguishesheaders which are merely used inside a local (class) context (like theinternal-header approach advocated in theC++ Annotations) and header fileswhich are used externally. Those latter header files need include guards toprevent them from being processed repeatedly by sources (indirectly) includingthem. Another, important, drawback is that a header file is processed againfor every source file including it. Such a task is not a trivial one. E.g., ifa header file includesiostream andstring then that forces a compilerlikeg++ 14.2.0 to process over 900,000 bytes of code for every sourcefile including that header.
To speed up compilations precompiled headers wereintroduced. Although the binary format of precompiled headers does indeedallow the compiler to parse the content of header files much faster thantheir standard text format, they are also very large. A precompiled headermerely includingiostream andstring exceeds 25 MB: a bit morethan its uncompiled text-file equivalent....
Modules were introduced to avoid those complications. Although modules canstill include header files, it's a good design principle to avoid includingheader files when designing modules. In general: once a module has beendesigned its use doesn't require processing header files anymore, andconsequenly programs that merely use modules are compiled much faster thancorresponding programs that use header files.
There is another, conceptual, feature of modules. The initial high-levelprogramming languages (like Fortran and Algol) (but also assembly languages)provided functions (a.k.a. subroutines and procedures) to distinguishconceptually different task levels. Functions implement specific tasks. Aprogram reading data, then processing the data, and finally showing theresults can easily be designed using functions, and is much easier tounderstand than replacing the function calls by their actual implementations:
int main() { readData(); processData(); showResults(); } Often such functions use their own support functions, etc, etc, untiltrivial decomposition levels are reached where simple flow control andexpression statements are used.This decomposition methodology works very good. It still does. But at theglobal level a problem does exist: there's little integrityprotection. Function parameters may help to maintain the program's dataintegrity, but it's difficult to ensure the integrity of global data.
In this respectclasses do a much better job. Theirprivate sectionsoffer means for class-designers to guarantee the integrity of the classes'data.
Modules allow us to take the next step up the (separation or integrity)ladder. Conceptually modules offer program sections which are completelyseparated from the rest of the program. Modules define what the outside worldcan use and reach, whether they are variables, functions, or types (likeclasses). Modules resemble factories: visitors can go to showrooms and meetinghalls, but the locations where the actual products are being designed andconstructed are not open to the public.
In this chapter we cover the syntax, design, and implementation of modules asoffered by theC++ programming language. To use modules with the currentedition of the Gnug++ compiler (version 14.2.0)--std=c++26 (or morerecent) should be specified as well as the module compilationflag-fmodules-ts. E.g.,
g++ -c --std=c++26 -fmodules-ts -Wall modsource.cc
Unfortunately, currently it's not all sunshine and roses. One (current?)consequence of using modules is that the standard that was specified whencompiling those modules is also required when compiling sources using thosemodules. If the specified standards differ (e.g., the modules were compiledwith option--std=c++26, but for a source file using those modules--std=c++23 was specified) then the compilation fails with an error like
error: language dialect differs 'C++26/coroutines', expected 'C++23/coroutines'A similar error is reported when the modules were compiled with
--std=c++23 and the module using source file is compiled specifying--std=c++26. Therefore, once a new standard becomes available, and amodule defining source files is recompiled using the new standard then modulesource files using that module must also be recompiled using that standard.Athttps://gcc.gnu.org/bugzilla/show_bug.cgi?id=103524 an overview ispublished of the (many) currently reported compiler bugs when modules arecompiled by Gnu'sg++ compiler. Many of those bugs refer to internalcompiler errors, sometimes very basic code that correctly compiles whenmodules are not used but that won't compile when using modules. Sometimesreported errors are completely incomprehensible. Another complexity isintroduced by the fact that, e.g., a class which is defined inside a module isno longer declared in an interface providing header file. Instead, it isdefined in a module defining source file. Consequently, those module-interfacedefining source files must be compiled before the member functions of such aclass can be compiled. But it's not the module's interface's object filethat's important at that point. When the module-interface defining source fileis compiled the compiler defines a`module-name'.gcm file in asub-directorygcm.cache (cf. section25.3). Whenever a sourcefile that uses the module is compiled the compiler must be able to read thatgcm.cache/module-name.gcm file. As a software engineer you cannot simplycompile such module using source files, butyou must ensure that thecompiler has access to the propermodule-name.gcm files.
Modules may define sub-components (partitions) defining facilities whichmay be completely inaccessible outside of the modulesthemselves. Partitions are covered in section25.6.
export module Name;Name is the module's name, its module-compiled interface unit becomes available in./gcm.cache/Name.gcm;module Name;module Name;, whereName is the module's name.export import Name;export import Name; specifications. Usingexport is optional. In module interface units specifyingexport means that the exported module is also available when sources import the module. E.g., // module interface unit: export module Group; export import <iostream>; ... // source file: import Group; int main() { // cout available via Group std::cout << "hello world\n"; }export module Name:Partition;Name is the module's name,Partition is apartition of moduleName. Its compiled interface unit becomes available in./gcm.cache/Name-Partition.gcm;export import :Partition;export import :Partition;. In module interface units usingexport is required, otherwise usingexport is optional.modname.cc' (wherename is the(possibly lowercase) module's name), located in a sub-directory having the(possibly lowercase) module's name. Using plain (internal) header files shouldbe avoided when defining and/or using modules.Here's an example of a module's interface. The module's name isSquare andit declares a function, a class, and a variable:
export module Square; // source: modsquare.cc export { double square(double value); class Square { double d_amount; public: Square(double amount = 0); // initialize void amount(double value); // change d_amount double amount() const; // return d_amount double lastSquared() const; // returns g_squared double square() const; // returns sqr(d_amount) }; } extern double g_squared; This module interface merely serves as an illustration. In practice moduleinterfaces don't contain many different items, but usually just a singleclass or, alternatively, a series of utility functions. For now, however, theslightly overpopulated moduleSquare is used as an initial illustration.The interface's top-line exports and defines the module name. This must be thefirst line of the module's interface unit. Next, the functionsquare andclass Square are declared inside anexport compound. It is possible to exportcomponentsindividually, but using an `export compound' is convenient. Exportedcomponents can be used outside of the module. Non-exported components (likeg_squared) are only available to the module's components, and aretherefore like global components, albeit with a restricted (module) scope.
Also note that the variableg_squared is listed in the interface asextern double g_squared: it is therefore adeclaration, not adefinition. Todefine variables in a module omitextern (as indouble g_squared), but to avoid overpopulating modules it's advised tomerely put declarations in module interface units.
Themodsquare.cc file can now be compiled, requiring the-fmodules-tscompiler option:
g++ -c --std=c++26 -fmodules-ts -Wall modsquare.cc
Compilingmodsquare.cc not only produces the filemodsquare.o, butalso a sub-directorygcm.cache, containing the `module compiled interfacefile'Square.gcm, which is somewhat comparable to a traditionalpre-compiled header file. This.gcm file must be available when compilingsource files implementing components of moduleSquare, and it must also beavailable to other source files thatimport (i.e., use) moduleSquare. Consequently, the directories containing such files must thereforealso havegcm.cache sub-directories containing theSquare.gcmfile. This requirement is complex, and in practice a so-calledmodmapper(cf. section25.7) is used to handle the complexity. A simple way tomake module compiled interface files available to all sections of a project isby defining a top-level directorygcm.cache and using soft-links (likesquare/gcm.cache) to the top-level'sgcm.cache directory. Thetop-levelgcm.cache directory and the soft-links in the project'ssub-directories can be prepared before compilingsquare/modsquare.ccresulting in the following directory structure:
. +-- gcm.cache | +-- Square.gcm | +-- square +-- gcm.cache -> ../gcm.cache
All components of the moduleSquare must specify that they're part ofthat module. Traditionally this is realized by using (internal) headerfiles. But projects using modules should no longer need header files. Insteadof the (traditionally used)square.h andsquare.ih files a moduleframe file, tailored to the modules' requirementscan be used when defining source files belonging to modules. In this examplethe requirement for the remaining source files of theSquare module issimple: just specify that the source file belongs to theSquaremodule. Here's a basicframe file, tailored to the members of theclassSquare:
module Square; Square::() { }Thefunction square isn't part of theclass Square, so when it'sdefined theSquare:: scope is omitted:
module Square; double square(double value) { return g_squared = value * value; }But the members of theclass Square can be defined as usual after copyingtheframe file to the source filew defining those members. Here isSquare's constructor:
module Square; Square::Square(double amount) : d_amount(amount) {}and the other members are defined analogously: module Square; void Square::amount(double value) { d_amount = value; } module Square; double Square::amount() const { return d_amount; } module Square; double Square::lastSquared() const { return g_squared; } module Square; double square(double value) { return g_squared = value * value; }As an aside: the members of this classSquare are all very simple, andinstead of defining them in separate source files theycould also bedefinedinline in themodule.cc file itself. Since the classSquare's interface is exported its members are too, and inlineimplementations of its members don't have to be provided in the export compound.
The module can now be used by, e.g., the program'smain function. Sourcefiles importing a module mustimport that module, and if multiple sourcefiles are defined in the top-level directory (likemain.cc, usage.cc,etc.), that directory can define its ownframe file. In this initialexample there's only a singlemain.cc source file, which merely has toimport moduleSquare. But since it also usesstd::cout it must importthemodule compiled system headeriostream (cf. section25.3.1):
import Square; import <iostream>; int main(int argc, char **argv) { std::cout << "the square of " << argc << " is " << square(argc) << '\n'; Square obj{12}; std::cout << "the square of 12 is " << obj.square() << "\n" "the last computed square is " << obj.lastSquared() << '\n'; }Thegcm.cache directories are only required during compilation time. Thelinker doesn't use them and once the source files have been compiled aprogram can be constructed as before, by linking the object files, resultingin a binary program.
This example illustrates several characteristics of modules:
export module followed by the module's name. Module names are identifiers, possibly followed by `. identifier' sequences (likeMy.Module).:name orname1:name2) are also encountered. Such names refer to modulepartitions, covered in section25.6.export module 'module-name' line. Therefore module interface files cannot define multiple modules;export compound or must be specified using their own initialexport keyword. If noexport is used the component is only accessible to the components of the module;import <iostream>;)are used, but preprocessor directives like#include <iostream> aren't..gcm file (the equivalent of a compiled header file) in thegcm.cache sub-directory of the module interface unit. If the module's nameisSquare, defined in themodsquare.cc source file then the compilerproducesgcm.cache/Sqaure.gcm as well as themodsquare.o objectfile. The.gcm file must be available to any source file importing themodule or implementing one of the module's components.Usually projects define several sub-directories, and files in thosesub-directories (as well as files in the top-level directory) may importmodules defined in other sub-directories. To ensure that module-compiledinterface units are available to all of the program's source files thefollowing setup can be adopted:
gcm.cache sub-directorygcm.cache -> ../gcm.cacheThis setup works fine as long as all the project's modules have differentnames, but that by itself is a good design principle.
std::string andstd:ostream. But when using modules including headersis deprecated and instead their module-compiled equivalents should be importedusingimport statements. To avoid recompiling system header files for different projects considerstoring module-compiled headers in/usr/include/c++/14 (here, '14' isg++'s main version number: update it to your actually installedversion). Using the procedure described in this section project source filescan import module-compiled system headers (assuming that projects have defined./gcm.cache directories as described in the previous section).
gcm.cache directory define the soft-linkusr -> /usr;iostream -> iostream.gcm) then as executeroot in/usr/include/c++/14 execute the commandg++ --std=c++26 -fmodules-ts -x c++-system-header iostream(if theC++ standard isn't
c++26 then adapt it to theC++ standard that should be used)iostream.gcm to the current directory:mv gcm.cache/usr/include/c++/14/iostream.gcm .
The same procedure can also be used to module-comile header files of installedlibraries. E.g., thebobcat(7) library stores its headers in/usr/include/bobcat. To module-compile itsarg header file:
cd /usr/includeg++ --std=c++26 -fmodules-ts -x c++-system-header bobcat/argmv gcm.cache/usr/include/bobcat/arg.gcm bobcat/Note: currently (usingg++ version 14.2.0) compilation sequence issues maybe encountered when module-compiling system headers. For some system headersmodule-compilation fails if some other module-compiled system headers arealready available. Those issues can usually be `solved' by first moving allexisting .gcm files to, e.g., a./tmp sub-directory, followed by themodule-compilation of the intended system header file, and then moving thetmp/*.gcm files back to the current directory.
To module-compile a standard header file (e.g., an existingsupport.hheader file in asupport sub-directory) to a module-compiled header fileissue the command
g++ --std=c++26 -Wall -fmodules-ts -x c++-header support.hThe compiler then writes the module-compiled header file
support.h.gcmin the./gcm.cache/,/ sub-directory (note: ingcm.cache's `comma'sub-directory).When a library is constructed there are usually multiple header files, betweenwhich some hierarchy may exist. Consider a library consisting of threeclasses:Top, depending onMiddle, which in turn depends onBaseEach class is defined in its own sub-directory. E.g.,base/base.h:
#ifndef INCLUDED_BASE_ #define INCLUDED_BASE_ #include <fstream> class Base { std::ifstream d_in; public: Base(); }; inline Base::Base() : d_in("demo.in") {} #endifmiddle/middle.h:
#ifndef INCLUDED_MIDDLE_ #define INCLUDED_MIDDLE_ #include <string> #include "../base/base.h" class Middle { std::string d_text; Base d_base; }; #endifandtop/top.h:
#ifndef INCLUDED_TOP_ #define INCLUDED_TOP_ #include <vector> #include "../middle/middle.h" class Top { std::vector<std::string> d_vect; Middle d_middle; }; #endifTo module-compile the library's headers thegcm.cache organization asdescribed in section25.2 is used (agcm.cache directory isdefined at the project's top-level directory, while sub-directories definegcm.cache soft-links to the top-levelgcm.cache directory). Next eachheader file is module-compiled whereafter thegcm.cache subdirectorycontains:
,/base.h.gcm ,/middle.h.gcm ,/top.h.gcm usr -> /usr
This, however, is not the organization which is expected by programs that needto use the module-compiled headers. To compare: the module-compiled systemheader files are, together with the system header files themselves, located in/usr/include/c++/14 (likeiostream andiostream.gcm). When module-compiled headers should be used by programs a similar organization isexpected. But that's easily realized by defining a sub-directory (e.g.,hdrs) containing soft-links to each of the library's header files andto each of the library's module-compiled header files. Ifhdrs is asub-directory of the libraary's top-level directory then it contains
base.h -> ../base/base.h base.h.gcm -> ../gcm.cache/,/base.h.gcm middle.h -> ../middle/middle.h middle.h.gcm -> ../gcm.cache/,/middle.h.gcm top.h -> ../top/top.h top.h.gcm -> ../gcm.cache/,/top.h.gcm
As elaborated in the following two sub-sections there are two ways programsusing the local library can import the library's module-compiled headers: byusing a relative path to thehdrs sub-directory or by using an absolutepath to thehdrs subdirectory.
hdr sub-directory they can be imported by sourcefiles which do not belong to the library.Suppose the library's header files are imported bymain.cc, defined in thedirectory~/project:
import "base.h"; import "middle.h"; import "top.h"; int main() { Base base; Middle middle; Top top; }Furthermore, assume the local library's top-level directory is~/support/locallib (and so~/support/locallib/hdrs contains thesoft-links to its header files and module-compiled header files).
To make the library's headers available tomain.cc two soft-links torelative destinations can be defined:
~/project define a soft-link to the library'shdrs directory:ln -s ../support/locallib/hdrs .
~/project/gcm.cache/,/ (note: comma!) sub-directory define a soft-link to the just definedhdrs soft-link:ln -s ../../hdrs .
Once these links are availablemain.cc can be compiled using
g++ -c --std=c++26 -fmodules-ts -isystem hdrs main.cc
hdr is located in a completely different location then the soft-link~/project/hdrs must point to that directory using a relative path specification.hdrs directory does not have to behdrs.Using the~/project/main.cc from the previous section define a soft-linkto the absolute location of the library'shdrs sub-directory in theproject'sgcm.cache sub-directory (note: here no comma is used):
ln -s ~/support/locallib/hdrs gcm.cacheNext compile
main.cc usingg++ -c -fmodules-ts -isystem ~/support/locallib/hdrs main.cc
gcm.cache the full path tohdrs is not required, but at least first sub-directory name must be be specified (since~/... refers to the user's home directory, which commonly begins at/home/... the link could be defined asln -s /home gcm.cache/).ln -s ~/support/locallib/hdrs /tmp/locallib ln -s /tmp gcm.cachethe compilation command is:
g++ -c -fmodules-ts -isystem /tmp/locallib main.cc
But, for the sake of completeness, local header filescan bemodule-compiled. But note that once a header file is compiledusingnamespace declarations specified in header files are lost when the headerfile is either included or imported. Compiled local header files arestored in thegcm.cache/,/ (note: a comma) sub-directory. They'reautomatically used when either their header files are included (e.g.,#include "header.h") or imported (usingimport "header.h";).
using namespacedeclarations avoiding the explicit namespace specification when referring tocomponents living in those namespaces.Here is a simple example of a module exporting a variable which is defined ina namespace:
export module NS; export import <cstddef>; export namespace FBB { extern size_t g_count; } It's a very basic example, illustrating the essence of defining a namespacecompletely covering the module's export compound. Variations are possible: anamespace could completely surround the export compund, a namespace could bedefined inside the export compound, only some of the module's components couldbelong to a namespace, non-exported components could belong to a namespace,multiple namespaces could be used, etc., etc..To use the variableg_count, its module is imported and its namespace isspecified as usual. E.g.,
import NS; import <iostream>; int main() { ++FBB::g_count; std::cout << FBB::g_count << '\n'; }Alternatively the source file could specify someusing namespacedeclarations so the repeated namespace specifications can be avoided:
import NS; import <iostream>; using namespace std; using namespace FBB; int main() { ++g_count; cout << g_count << '\n'; }Some notes:
export module modules cannot be defined inside namespaces. Constructions like namespace Area { export module Nested; ... declarations of components of Nested } won't compile. But as shown, defining a namespace section inside a module is fine.import declarations cannot be nested (i.e., they must be at global scope) So constructions like the following also won't compile: namespace FBB { import NS; }Since modules are defined in source files there are no header files anymorewhen using modules. Modules specify their components in source files,replacing the traditional header files: by defining templates in a moduleinterface unit the template's recipe character is kept, and they areinstantiated when needed.
Here's an initial example. Amodule Adder exports a function templateadd adding two values and returning their sum:
export module Adder; export { template <typename Type> Type add(Type const &t1, Type const &t2) { return t1 + t2; } }Source files importingAdder can now add values of types supporting the+ operator:
import Adder; import <iostream>; import <string>; using namespace std; int main() { cout << add(1, 2) << '\n' << add(1.1, 2.2) << '\n' << add("hello "s, "world"s) << '\n'; }producing the following output:
3 3.3 hello world
SortMap offers the facilities of anunordered_map, but alsohas twosort members. Both return a vector with pointers to the elementsof the unordered map. One member returns pointers to the elements sorted bytheir key values, the other member receives a functor, returning a vector ofpointers to the elements which are sorted according to the functor'sdecisions.The module interface unit defines theSortMap module. It declares theclass templateSortMap. The module, like any module, can be imported inother source files, like a source filemain.cc. Themain functioninserts its arguments intoSortMap and then shows the inserted values:first ordered by the map's keys, then ordered by the map's values:
import SortMap; import <iostream>; import <string>; using namespace std; int main(int argc, char **argv) { SortMap<string, size_t> sortMap; for (; argc--; ) // fill sortMap sortMap.emplace(argv[argc], argc); for (auto const *ptr: sortMap.sort()) // sort by key cout << ptr->first << ' ' << ptr->second << "; "; cout.put('\n'); for (auto const *ptr: sortMap.sort( // sort by value [&](auto const &lhs, auto const &rhs) { return lhs->second < rhs->second; } ) ) cout << ptr->first << ' ' << ptr->second << "; "; cout.put('\n'); }When callinga.out one two three four five six it outputs
a.out 0; five 5; four 4; one 1; six 6; three 3; two 2 a.out 0; one 1; two 2; three 3; four 4; five 5; six 6
All members of class templates are templates, and as with header filescontaining class templates there's only one source file defining the moduleinterface unit: it contains the class template's interface and also theimplementations of its members. There is, however, an escape route: it'scovered in section25.6. But in this section the standard way to implement class templates is covered.
When using a single file there's maybe one drawback: the module interfaceunit, as it must contain the full implementation of the class template, mayquickly grow to a very large file, which is hard to maintain. To simplifymaintenance it is advised to adopt here theone function, one file designprinciple also: the module interface unit itself exports the class interface,and then uses an#include preprocessor directive to add the class members'implementations:
export module SortMap; export import <unordered_map>; export import <vector>; import <algorithm>; export { template <typename Key, typename Value> class SortMap: public std::unordered_map<Key, Value> { using UMap = std::unordered_map<Key, Value>; using ValueType = typename UMap::value_type; using Vect = std::vector<ValueType const *>; private: Vect d_sortVect; public: Vect const &sort(); // sort the keys // 1.f template <typename Functor> // use a functor // 2.f Vect const &sort(Functor const &functor); }; } #include "sortmap.f"The design of theclass SortMap itself is standard. At the top of themodule interface unit theSortMap module name is defined followed byimporting some module compiled system header files. Sincealgorithm isonly used internally by the member functions it's not exported. But importedcomponentsmust be specified at the top of module source files, and soalgorithm cannot be imported by, e.g.,sortmap.f.
The filesortmap.f itself merely includes the files implementing the twosort members, keeping the module interface unit as clean as possible. Hereissortmap.f:
#include "sort1.f" #include "sort2.f"
Each of its included files contain the definition of one memberfunction. A comment in the class's interface indicates which function isdefined in which file. Here's the firstsort member (sort1.f):
template <typename Key, typename Value> SortMap<Key, Value>::Vect const &SortMap<Key, Value>::sort() { d_sortVect.clear(); for (auto const &el: *this) d_sortVect.push_back(&el); std::sort(d_sortVect.begin(), d_sortVect.end(), [&](auto const *lhs, auto const *rhs) { return lhs->first < rhs->first; } ); return d_sortVect; }And here's the secondsort member, accepting a functor (sort2.f)
template <typename Key, typename Value> template <typename Functor> SortMap<Key, Value>::Vect const &SortMap<Key, Value>::sort( Functor const &functor) { d_sortVect.clear(); for (auto const &el: *this) d_sortVect.push_back(&el); std::sort(d_sortVect.begin(), d_sortVect.end(), functor); return d_sortVect; }In the introduction section of this chapter it was stated that modules offernew, conceptual features: modules offer program sections which are completelyseparated from the rest of the program, but modules themselves can definesubsections (calledpartitions) which can define components(classes, types, variables, etc.) which areonly accessible to the moduleand its partitions.
Within the restriction that partitions can be defined so that they're onlyaccessible to their module and their sibling-partitions the access rights ofclasses defined in partitions are identical to those of classes ingeneral: public members are accessible to their users, protected members areavailable to classes derived from those classes, while private members cannotbe accessed from outside of those classes.

Figure36 illustrates a simple module (Math) having twopartitions. It shows the relationships between the module and its partitionsin the same way as commonly used for classes: the most basic (least dependent)partition is on top, the partition depending on the topmost partition is inthe middle, and the module itself, depending on both partitions, is at thebottom. TheMath module defines a classMath, having a memberreturning the sum of twosize_t values and a membercount returningthe number of performed additions. It's a very basic design merelyillustrating the way partitions are designed.
The object ofclass Add performs an addition, and the object ofclassUtility keeps track of the number of additions that were performed.BothAdd andUtility are defined as classes in their own partitions:Math:Utility andMath:Add.
Note that partitions themselves are not classes. Whendefining apartition interface unit only a single colon is used insteadof two, as used then defining cass member functions (soMath:Utility andnotMat::Utility).
TheMath interface unit exportsclass Math, allowing code which doesnot belong to the moduleMath itself to defineMath objects afterimporting theMath module:
import Math; void fun() { Math math; ... }Since theclass Math has data membersUtility d_util andAdd d_addthese classes must be known inside theMath module interface unit(modmath.cc), but also by software defining objects ofclass Math(comparable to the requirement to, e.g., include<string> when a classdefines astd::string data member). Therefore theMath moduleinterface unit specifiesexport import for theUtility andAddpartitions. As we'll shortly see theseexport specifications do not implythat software merely using the facilities of theMath module can alsodefine, e.g.,Math:Utility objects: the partitions can still remain`private' to theMath module. Here is theMath module interface unit,defined inmath/modmath.cc:
export module Math; export import :Utility; export import :Add; export { class Math { Utility d_util; Add d_add; public: Math(); size_t count() const; size_t add(size_t lhs, size_t rhs); }; }The design of the moduleMath defining partitions introduces a completelynew level of separation: the components of partitions are like nested classesbut don't result in `class overpopulation': partitions are not definedinside modules but are completely separately defined from their modules,while making their facilitiesonly available to their modules and siblingpartitions. And although probably not illustrating good design: by definingexport compounds in partition interface units the elements in thosecompounds become directly available to using software essentially turning thepartition into a module.
Math:Utility partition is the most basic of the twoMathpartitions. It does not depend on features of either theMath module ortheMath:Add partition. Since itdoes use thesize_t type, itspecifiesexport import <cstddef>. Here, because of itsexportspecification, the definitions incstddef are also available whenUtility is imported, and since theMath module itself exports:Utility also to software importingMath.AsUtility is aMath partition its partition interface unit starts byspecifing this:
export module Math:Utility;The colon indicates that this is not a plain module but a partition (notethat the first linemust start by specifying
export). It containsonly non-exported components, but as it's a partition of a module, all thepartition's components are fully available to its module and its siblingpartitions. Itsclass Utility has two simple members, both very simple,never changing one-liners. Because of that they're implemented in-line. Here'stheUtility partition interface unit: export module Math:Utility; export import <cstddef>; class Utility { size_t d_count; public: Utility(); void inc(); size_t count() const; }; inline void Utility::inc() { ++d_count; } inline size_t Utility::count() const { return d_count; }Defining members inline is not required, but is an option. For example, itsconstructor could very well also have been defined inline, but (forillustration purposes) is defined in a separate source file. AsUtility'sconstructor is a partition source file it starts by specifying its module(Math), followed by importing the partition component (:Utility) whichis defined in this source file:
module Math; import :Utility; Utility::Utility() : d_count(0) {}Math:Utility partition theMath:Add partitiondoes depend on another partition: it depends onMath:Utility. LikeUtility's modutility.cc it starts itsmodadd.cc file by exporting itspartition name. But then it imports:Utility, since that partition is usedbyMath:Add, making availableall components of that partition.Since:Utility already imports<sstddef> :Add can also usesize_t. For:Add the:Utility partition wouldn't have to specifyexport in front ofimport <stddef> since all components of importedpartitions are available to both the module and its partitions. But by usingexport import <cstddef> users of theMath module automatically receive<cstddef>'s definitions (among whichsize_t). Here's theMath::Addpartition interface unit (again: notice that the interface doesn't contain anexport compound):
export module Math:Add; import :Utility; class Add { Utility &d_utility; public: Add(Utility &utility); size_t sum(size_t lhs, size_t rhs); };For this partition no members were defined inline (although that would alsohave been possible). Instead all members were defined in separate sourcefiles. Here isclass Add's constructor:
module Math; import :Add; // export import :Utility; OK, but superfluous Add::Add(Utility &utility) : d_utility(utility) {}and itssum member:
module Math; import :Add; size_t Add::sum(size_t lhs, size_t rhs) { d_utility.inc(); return lhs + rhs; }Math module and its partitions have beendefined (cf. figure36) theMath module's members can be defined. The constructor ofclass Add in theMath:Add partitionneeds a reference to aUtility object which it receives fromMathconstructor: #include "math.i" //import Math; Math::Math() : d_add(d_util) {}Sinceclass Utility's count member is public, it can be called by theMath::count member:
import Math; size_t Math::count() const { return d_util.count(); }and finally,Math's add member callsAdd's sum member, defined in theMath:Add partition to obtain the sum of two positive integral values:
import Math; size_t Math::add(size_t lhs, size_t rhs) { return d_add.sum(lhs, rhs); }Math module has been developed it can be used by aprogram. The exported facilities offered by a module are available afterimporting the module. Note that although a module mayexport import itspartitions only partition components defined in theirexport compound areavailable to source files not belonging to the module itself. AsMath:Utility exports<cstddef> source files importingMathautomatically also can usecstddef's features.Here's amain function importingMath and using its facilities to addtwo positive integral numbers. Header files are not used anymore, and theimplementation of themain function is equal to the implementation whenusing header files. However, compared to the latter implementation thecompilation of a program using modules requires significantly less time, andthe design of modules using partitions may offer better isolation ofcomponents that are mere building blocks of the those modules.
Here is the implementation of themain function using theMath module:
import Math; import <iostream>; using namespace std; int main() { Math math; cout << "Initial number of additions: " << math.count() << "\n" "Enter two pos. values to add: "; size_t lhs; size_t rhs; cin >> lhs >> rhs; cout << "their sum is " << math.add(lhs, rhs) << "\n" "total number of performed additions: " << math.count() << '\n'; }SortMap was defined in amodule interface unit. Like class templates in header files class templates ina module interface files may grow large because the file not only contains theclass template's interface but also the implementations of itsmembers. Consequently a full recompilation is required when only a singlemember function is modified. Recompilations cannot completely be avoided, butby using module partitions complete recompilation can often be avoided.Some maintenance complications, however, remain: once a class template'smember is modified the modification is not visible in members that use thatmember. But this dependency issue always plays a role when using templates:once a template has been modified all code using the template needs to berecompiled.
In the current section class templates are implemented using modulepartitions, each containing a member of the class template. The previouslydevelopedSortMap module consists of three components: the class interfaceand twosort members functions. Now when using partitions eachcomponent will be defined in its own partition, and the module itself containsexport import statements for each partition which must be accessible bycode using the module. Consequently themodsortmap.cc module interfaceunit is remarkably simple:
export module SortMap; export import :Interface; export import :Sort1; export import :Sort2;
Note that even the class interface is defined in a partition: it cannot bedefined in the module interface unit itself, since partitionscan dependon other partitions, but not on the module itself as the partitions must (asthey are imported by the module) be available before the module itself can becompiled. Partitions, however,can depend on other partitions, so apartition implementing asort function can import a partition providingthe class's interface.
As the:Interface partition merely specifies what's offered by theclass SortMap it doesn't depend on the other partitions. So it merelydeclares but does not contain the implementations of thesort members, andthus it does not itself have to import thealorithm module-compiledheader:
export module SortMap:Interface; export import <unordered_map>; export import <vector>; export { template <typename Key, typename Value> class SortMap: public std::unordered_map<Key, Value> { using UMap = std::unordered_map<Key, Value>; using ValueType = typename UMap::value_type; using Vect = std::vector<ValueType const *>; private: Vect d_sortVect; public: Vect const &sort(); // sort the keys // 1.f template <typename Functor> // use a functor // 2.f Vect const &sort(Functor const &functor); }; }On the other hand, the implementations of the twosort membersdodepend on the interface, since they implement their declarations. Theyalso depend on the facilities provided by thealgorithm module-compiledheader. Both are therefore imported. Here's the implementation of the firstsort member in the:Sort1 partition:
export module SortMap:Sort1; import :Interface; import <algorithm>; export { template <typename Key, typename Value> SortMap<Key, Value>::Vect const &SortMap<Key, Value>::sort() { d_sortVect.clear(); for (auto const &el: *this) d_sortVect.push_back(&el); std::sort(d_sortVect.begin(), d_sortVect.end(), [&](auto const *lhs, auto const *rhs) { return lhs->first < rhs->first; } ); return d_sortVect; } }The secondsort member is implemented analogously:
export module SortMap:Sort2; import :Interface; import <algorithm>; export { template <typename Key, typename Value> template <typename Functor> SortMap<Key, Value>::Vect const &SortMap<Key, Value>::sort( Functor const &functor) { d_sortVect.clear(); for (auto const &el: *this) d_sortVect.push_back(&el); std::sort(d_sortVect.begin(), d_sortVect.end(), functor); return d_sortVect; } }Themain function can remain as provided in section25.5.1. Whenconstructing the program the partition dependencies must be taken intoaccount:
:Interface partition is compiled;:Sort1 and:Sort2 partitions are compiled;main.cc is compiled and the compiled object files are linked resulting in the final program.With modules traditional headers (.h files) should be avoided. Systemheader files (cf. section25.3.1) still exist, but for thoseimportstatements should be used or made available. For locally developed librariesmodule-compiled header files can also be used (cf. section25.3.2).
It can be difficult to determine which module sections canimmediately be compiled, and which ones depend on already compiledsections. Moreover, source files belonging to modules but not defining theirinterface units may import modules which aren't yet available whencompiling the module's interface unit. For example, moduleBasic's interface unit is independent of other modules:
export module Basic; export { class Basic { public: void hello() const; }; }But a moduleSecond importsBasic and therefore the compilation ofits module interface file depends on the availability ofgcm.cache/Basic.gcm:
export module Second; export import Basic; export import <iosfwd>; export { class Second { public: static std::string hello(); }; }Consequentlybasic/modbasic.cc must be compiled before compilingsecond/modsecond.cc. However,moduleSecond may export aclassSecond, and an object ofclass Secondcould be defined by a memberof theclassBasic, exported by theBasic module:
module Basic; import Second; import <iostream>; import <string>; using namespace std; void Basic::hello() const { cout << Second::hello() << '\n'; }In such situations the module interface units must first, and in the rightorder, be compiled. Then, once they are available the remaining source filesof the modules can be compiled.
This two-step process (first compile the module interface units in the rightorder and then compile the remaining source files) quickly becomes achallenge, which is commonly handled by using amodule mapper. Modulemappers inspect the modules of a project, building their dependency tree, andmaybe compiling the module interface units in the right order. The modulemappericmodmap(1), a utility program of theicmake(1) project, issuch a module mapper.
By default (but configurable via options)icmodmap(1) expect projectsusing modules to be organized as follows:
CLASSES, where each line (ignoring empty lines andC++ comment) specifies the name of a sub-directory implementing one of the project's components. Partitions can be defined in their own sub-directories or in sub-directories of the module to which they belong. Sub-directories do not have to define modules, but all of the project's source filesmay import modules;main.cc defining themain function);mod, followed by the (optionally lower-case) name of the module or partition they define. Following this convention a moduleSupport is defined in the filemodsupport.cc;Icmodmap(1) inspects each of the source files in the project's top-leveldirectory and in each sub-directory specified in theCLASSES file. If asource file's first line starts withexport module it's a module orpartition interface unit. If so, its module or partition name is stored in adata-base. Since module and partition interface units may themselves importmodules and/or partitions the current interface depends on those importedcomponents. Those imported components are declared in its data-baseregistering that the current interface unit depends on those importedcomponents.
Likewise, if another source file imports modules or implements a component ofa module or partition then those modules (and/or paritions) are also declaredin its data-base.
Once all source files were inspectedicmodmap(1) determines the moduledependencies: its data-base must indicate that each imported module orpartition also has a module/partition interface unit, and that there are nocircular dependencies among the module/partition interface units. If theserequirement are satified thenicmodmap by default calls the compiler tocompile the interface units in their proper order. By default the object filesare stored in the standardicmake(1) fashion: each compiled interface unitis stored in the project'stmp/o sub-directory and their names begin witha number which is equal to the line number in theCLASSES file specifyingthe inspected sub-directory (like1modfirst.o, 2modsecond.o,) using prefix0 for module(s) defined in the project's top-level directory.
When the inspection and compilation successfully completes then the interfaceunits' object files are stoed intmp/o, and the project's top-leveldirectory contains a sub-directorygcm.cache containing the.gcm filesof all modules and partitions. In addition each inspected sub-directory hasreceived a soft-linkgcm.cache to its parent'sgcm.cachesub-directory, so each of the project's source files can import each module.
Now that the module and parition interface files are available the remainingsource files can be compiled as usual. They can import and use the componentsof the the defined modules (and where applicable the defined paritions).
When a module interface unit is modified and it's recompiled then the modifieddefinition replaces the old one. Consider this interface unit:
export module Demo; export { class Demo { int d_value = 100; public: int value() const; }; } The member functionDemo::value (returningd_value) is implementedin its own source file, and is called bymain: import Demo; import <iostream>; using namespace std; int main() { Demo demo; cout << demo.value() << '\n'; }When the program is run it outputs 100. Now the module interface unit ismodified:d_first is added to the class as its first data member:
export module Demo; export { class Demo { int d_first = 13; int d_value = 100; public: int value() const; }; }Nextmoddemo.cc andmain.cc, are recompiled and the three object filesare linked constructing the binary program. The new binary outputs 13:value's object file returns the first bytes of theDemo object as anint, but since the classDemo was modified those bytes no longercontain the value 100.
Such complications are familiar: when using a traditional header declaring aclass and the class changes the organization of its data members then inpractice all source files using the class must be recompiled (and recursively:if another class is derived from, or contains a data member of the modifiedclass then all source files using the other class must also berecompiled). When using modules such recompilations are also required. Theicmodmap(1) support program has an option to recompile module (partition)interface units depending on modified interface units and either to modify thelast write times of source files using those modules or to write their namesto a file so a build utility can recompile those module using source files.
What if a library's design merely uses modules? In that case the library mustmake available its module-compiled interface files (i.e.,.gcm files) toits users. Copying the library's.gcm files to thegcm.cachesub-directory of a using project is not necessary (and probably a bad designanyway, since modifications of the library's.gcm files will not benoted), but soft-links should be used. However, often modules import othermodules or partitions, and consequently such library-modules in turn maydepend on other modules (or partitions). When a project must import alibrary's module not only the module's.gcm file must be available butalso (recursively) the module/partition.gcm files that module dependson.
As an illustration consider the situation where a project defines threemodules:Module1, Module2, andModule3. Module1 importsExternal,which is a module offered by some library;Module2 importsModule1, andModule3 importsModule2 (cf. figure37).

TheExternal module doesn't belong to the current project, and so theExternal.gcm file lives elsewhere. When constructing the project itsgcm.cache directory must makeExternal.gcm available. This is realizedby defining a soft-link to the actual location ofExternal.gcm. But inaddition soft-links to the modules/partitions imported byExternal.gcmmust be made available in the project'sgcm.cache sub-directory.
Theicmodmap(1) support program can be used to determine and to satisfythe requirements of externally defined modules. To determine the modules' dependenciesicmodmap is called in theproject's top-level directory specifying its--dependencies (or-d)option. It shows the dependencies amount the modules and reports an error asthe moduleExternal isn't found:
[Error 1] 1 UNKNOWN module Dependencies: LOCAL module Module1 imports UNKNOWN module External UNKNOWN module External LOCAL module Module2 imports LOCAL module Module1 LOCAL module Module3 imports LOCAL module Module2And indeed, there's an error: the
External module wasn't developed inthe context of the current project. It's defined in an external library,offering its module/partition compiled interface units to its users. Byinformingicmodmap where the external.gcm files are located the erroris solved. The relative or absolute path to the directory containing thelibrary's.gcm files is either specified as a command-line option or it'sspecified in a file. Either of these can be passed toicmodmap using its--extern (or-e) option. E.g., if the library's.gcm files are in/tmp/library/gcm.cache then by callingicmodmap -d -e /tmp/library/gcm.cachethe error disappears and
icmodmap reports:Dependencies: LOCAL module Module1 imports EXTERN module External EXTERN module External LOCAL module Module2 imports LOCAL module Module1 LOCAL module Module3 imports LOCAL module Module2specifying
--extern also defines the soft-links to the externalmodules: the project'sgcm.cache sub-directory now contains the soft-linkExternal.gcm -> /tmp/library/gcm.cache/External.gcmNow that the requirements of the project's module interface files are allsatisfied they can be compiled (they are compiled by
icmodmap if the-d option isn't specified), followed by the compilation of the project's remaining source files. Finally, all object files can be linked to the usedobject files of the external library in the usual way (specifying, e.g., thelinker's-L and-l options).mod, followed by the lower-case module- or partition-names, using.cc extensions (e.g.modgroup.cc);./group)Group is defined in./group, than its partitonGroup:Support is defined in./group/support;gcm.cache, initially containing a soft-linkusr/ to the system's/usr sub-directory allowing source files to import module-compiled system header files (e.g.,import <iostream>;);gcm.cache to../gcm.cache, so all compiled module- and partition-interface units are available in the (top-level)gcm.cache sub-directory.The compiler writes compiled interface units in theirgcm.cachesub-directories. The.gcm filenames of modules are equal to the names of themodules (e.g., forGroup it isGroup.gcm). The filename ofa module-compiled partition starts with its module name, followed by a dash(-), followed by the partitions's name (e.g., forGroup:Support it isGroup-Support.gcm).
To avoid overfilling interface units they should (like class headers) notdefine components but merelydeclare them. Their definitions areprovided in separate source files, defined in their interface unit'ssub-directory, using the familiar theone component, one source filedesign principle.
Math:Add partition (cf. section25.6)all start withmodule Math; import :Add;Since the
Math:Add partition interface unit declares aclass Addmost or all of its source files implement a member ofclass Add. In thesecases it's convenient to have a predefinedframe file containing theessential elements of its source files. E.g., forMath::Add sources thiscould be module Math; import :Add; using namespace std; void Add::() { } When implementing a member copy theframe to the intended source file,specify the member's name, define its body and maybe change its return type.