Chapter 25: Modules

Since the introduction of header files in the C programming language header files have been the main tool for declaring elements that are not defined but are used in source files. E.g., to use printf in main the preprocessor directive #include <stdio.h> had to be specified.

Header files are still extensively used in C++, but gradually some drawbacks emerged. One minor drawback is that, in C++, header files frequently not merely contain function and variable declarations but often also type definitions (like class interfaces and enum definitions). When designing header files the software engineer therefore commonly distinguishes headers which are merely used inside a local (class) context (like the internal-header approach advocated in the C++ Annotations) and header files which are used externally. Those latter header files need include guards to prevent them from being processed repeatedly by sources (indirectly) including them. Another, important, drawback is that a header file is processed again for every source file including it. Such a task is not a trivial one. E.g., if a header file includes iostream and string then that forces a compiler like g++ 14.2.0 to process over 900,000 bytes of code for every source file including that header.

To speed up compilations precompiled headers were introduced. Although the binary format of precompiled headers does indeed allow the compiler to parse the content of header files much faster than their standard text format, they are also very large. A precompiled header merely including iostream and string exceeds 25 MB: a bit more than its uncompiled text-file equivalent....

Modules were introduced to avoid those complications. Although modules can still include header files, it's a good design principle to avoid including header files when designing modules. In general: once a module has been designed its use doesn't require processing header files anymore, and consequenly programs that merely use modules are compiled much faster than corresponding programs that use header files.

There is another, conceptual, feature of modules. The initial high-level programming languages (like Fortran and Algol) (but also assembly languages) provided functions (a.k.a. subroutines and procedures) to distinguish conceptually different task levels. Functions implement specific tasks. A program reading data, then processing the data, and finally showing the results can easily be designed using functions, and is much easier to understand than replacing the function calls by their actual implementations:

    int main()
    {
        readData();
        processData();
        showResults();
    }
Often such functions use their own support functions, etc, etc, until trivial decomposition levels are reached where simple flow control and expression statements are used.

This decomposition methodology works very good. It still does. But at the global level a problem does exist: there's little integrity protection. Function parameters may help to maintain the program's data integrity, but it's difficult to ensure the integrity of global data.

In this respect classes do a much better job. Their private sections offer 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 completely separated from the rest of the program. Modules define what the outside world can use and reach, whether they are variables, functions, or types (like classes). Modules resemble factories: visitors can go to showrooms and meeting halls, but the locations where the actual products are being designed and constructed are not open to the public.

In this chapter we cover the syntax, design, and implementation of modules as offered by the C++ programming language. To use modules with the current edition of the Gnu g++ compiler (version 14.2.0) --std=c++26 (or more recent) should be specified as well as the module compilation flag -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 when compiling those modules is also required when compiling sources using those modules. If the specified standards differ (e.g., the modules were compiled with 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 a module defining source files is recompiled using the new standard then module source files using that module must also be recompiled using that standard.

At https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103524 an overview is published of the (many) currently reported compiler bugs when modules are compiled by Gnu's g++ compiler. Many of those bugs refer to internal compiler errors, sometimes very basic code that correctly compiles when modules are not used but that won't compile when using modules. Sometimes reported errors are completely incomprehensible. Another complexity is introduced by the fact that, e.g., a class which is defined inside a module is no longer declared in an interface providing header file. Instead, it is defined in a module defining source file. Consequently, those module-interface defining source files must be compiled before the member functions of such a class can be compiled. But it's not the module's interface's object file that's important at that point. When the module-interface defining source file is compiled the compiler defines a `module-name'.gcm file in a sub-directory gcm.cache (cf. section 25.3). Whenever a source file that uses the module is compiled the compiler must be able to read that gcm.cache/module-name.gcm file. As a software engineer you cannot simply compile such module using source files, but you must ensure that the compiler has access to the proper module-name.gcm files.

25.1: Syntax

Modules introduce new syntax to C++. In this section the new syntax is summarized. Note that each of the following syntax specifications end in semicolons.

Modules may define sub-components (partitions) defining facilities which may be completely inaccessible outside of the modules themselves. Partitions are covered in section 25.6.

25.2: Modules

Modules (and partitions, cf. section 25.6) are designed using source files instead of header files. A module always defines a module interface unit. The module's interface unit is the module's equivalent of a header file. Consider (as used in the C++ Annotations) defining the module's interface in a file named `modname.cc' (where name is the (possibly lowercase) module's name), located in a sub-directory having the (possibly lowercase) module's name. Using plain (internal) header files should be avoided when defining and/or using modules.

Here's an example of a module's interface. The module's name is Square and it 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 module interfaces don't contain many different items, but usually just a single class or, alternatively, a series of utility functions. For now, however, the slightly overpopulated module Square is used as an initial illustration.

The interface's top-line exports and defines the module name. This must be the first line of the module's interface unit. Next, the function square and class Square are declared inside an export compound. It is possible to export componentsindividually, but using an `export compound' is convenient. Exported components can be used outside of the module. Non-exported components (like g_squared) are only available to the module's components, and are therefore like global components, albeit with a restricted (module) scope.

Also note that the variable g_squared is listed in the interface as extern double g_squared: it is therefore a declaration, not a definition. To define variables in a module omit extern (as in double g_squared), but to avoid overpopulating modules it's advised to merely put declarations in module interface units.

The modsquare.cc file can now be compiled, requiring the -fmodules-ts compiler option:

   g++ -c --std=c++26 -fmodules-ts -Wall modsquare.cc

Compiling modsquare.cc not only produces the file modsquare.o, but also a sub-directory gcm.cache, containing the `module compiled interface file' Square.gcm, which is somewhat comparable to a traditional pre-compiled header file. This .gcm file must be available when compiling source files implementing components of module Square, and it must also be available to other source files that import (i.e., use) module Square. Consequently, the directories containing such files must therefore also have gcm.cache sub-directories containing the Square.gcm file. This requirement is complex, and in practice a so-called modmapper (cf. section 25.7) is used to handle the complexity. A simple way to make module compiled interface files available to all sections of a project is by defining a top-level directory gcm.cache and using soft-links (like square/gcm.cache) to the top-level's gcm.cache directory. The top-level gcm.cache directory and the soft-links in the project's sub-directories can be prepared before compiling square/modsquare.cc resulting in the following directory structure:

    .
    +-- gcm.cache
    |   +-- Square.gcm
    |
    +-- square
        +-- gcm.cache -> ../gcm.cache

All components of the module Square must specify that they're part of that module. Traditionally this is realized by using (internal) header files. But projects using modules should no longer need header files. Instead of the (traditionally used) square.h and square.ih files a module frame file, tailored to the modules' requirements can be used when defining source files belonging to modules. In this example the requirement for the remaining source files of the Square module is simple: just specify that the source file belongs to the Square module. Here's a basic frame file, tailored to the members of the class Square:

    module Square;

    Square::()
    {
    }

The function square isn't part of the class Square, so when it's defined the Square:: scope is omitted:

    module Square;

    double square(double value)
    {
        return g_squared = value * value;
    }

But the members of the class Square can be defined as usual after copying the frame file to the source filew defining those members. Here is Square'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 class Square are all very simple, and instead of defining them in separate source files they could also be defined inline in the module.cc file itself. Since the class Square's interface is exported its members are too, and inline implementations of its members don't have to be provided in the export compound.

The module can now be used by, e.g., the program's main function. Source files importing a module must import that module, and if multiple source files are defined in the top-level directory (like main.cc, usage.cc, etc.), that directory can define its own frame file. In this initial example there's only a single main.cc source file, which merely has to import module Square. But since it also uses std::cout it must import the module compiled system header iostream (cf. section 25.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';

    }

The gcm.cache directories are only required during compilation time. The linker doesn't use them and once the source files have been compiled a program can be constructed as before, by linking the object files, resulting in a binary program.

This example illustrates several characteristics of modules:

25.3: The gcm.cache directory

When using modules the module interface units must be compiled before other sources using the modules or implementing their components can be compiled. This also holds true for partition interface units (cf. section 25.6). When a module interface unit is compiled the compiler stores the module's .gcm file (the equivalent of a compiled header file) in the gcm.cache sub-directory of the module interface unit. If the module's name is Square, defined in the modsquare.cc source file then the compiler produces gcm.cache/Sqaure.gcm as well as the modsquare.o object file. The .gcm file must be available to any source file importing the module or implementing one of the module's components.

Usually projects define several sub-directories, and files in those sub-directories (as well as files in the top-level directory) may import modules defined in other sub-directories. To ensure that module-compiled interface units are available to all of the program's source files the following setup can be adopted:

This setup works fine as long as all the project's modules have different names, but that by itself is a good design principle.

25.3.1: Module-compiled system header files

Traditionally system header files were included to declare classes like std::string and std:ostream. But when using modules including headers is deprecated and instead their module-compiled equivalents should be imported using import statements.

To avoid recompiling system header files for different projects consider storing module-compiled headers in /usr/include/c++/14 (here, '14' is g++'s main version number: update it to your actually installed version). Using the procedure described in this section project source files can import module-compiled system headers (assuming that projects have defined ./gcm.cache directories as described in the previous section).

The same procedure can also be used to module-comile header files of installed libraries. E.g., the bobcat(7) library stores its headers in /usr/include/bobcat. To module-compile its arg header file:

Note: currently (using g++ version 14.2.0) compilation sequence issues may be encountered when module-compiling system headers. For some system headers module-compilation fails if some other module-compiled system headers are already available. Those issues can usually be `solved' by first moving all existing .gcm files to, e.g., a ./tmp sub-directory, followed by the module-compilation of the intended system header file, and then moving the tmp/*.gcm files back to the current directory.

25.3.2: Module-compiled headers of locally developed libraries

In addition to header files used for system libraries locally developed libaries exist. Such libraries may very well have traditionally been developed, using standard header files. But once project start using modules it might be attractive to convert the traditional header files to module-compiled header files.

To module-compile a standard header file (e.g., an existing support.h header file in a support sub-directory) to a module-compiled header file issue the command

    g++ --std=c++26 -Wall -fmodules-ts -x c++-header support.h
The compiler then writes the module-compiled header file support.h.gcm in the ./gcm.cache/,/ sub-directory (note: in gcm.cache's `comma' sub-directory).

When a library is constructed there are usually multiple header files, between which some hierarchy may exist. Consider a library consisting of three classes: Top, depending on Middle, which in turn depends on Base Each 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")
    {}

    #endif

middle/middle.h:

    #ifndef INCLUDED_MIDDLE_
    #define INCLUDED_MIDDLE_

    #include <string>

    #include "../base/base.h"

    class Middle
    {
        std::string d_text;
        Base d_base;
    };

    #endif

and top/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;
    };

    #endif

To module-compile the library's headers the gcm.cache organization as described in section 25.2 is used (a gcm.cache directory is defined at the project's top-level directory, while sub-directories define gcm.cache soft-links to the top-level gcm.cache directory). Next each header file is module-compiled whereafter the gcm.cache subdirectory contains:

    ,/base.h.gcm
    ,/middle.h.gcm
    ,/top.h.gcm
    usr -> /usr

This, however, is not the organization which is expected by programs that need to use the module-compiled headers. To compare: the module-compiled system header files are, together with the system header files themselves, located in /usr/include/c++/14 (like iostream and iostream.gcm). When module-compiled headers should be used by programs a similar organization is expected. But that's easily realized by defining a sub-directory (e.g., hdrs) containing soft-links to each of the library's header files and to each of the library's module-compiled header files. If hdrs is a sub-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 programs using the local library can import the library's module-compiled headers: by using a relative path to the hdrs sub-directory or by using an absolute path to the hdrs subdirectory.

25.3.2.1: Using a relative location

Once the local library's headers and module-compiled headers are available via the local library's hdr sub-directory they can be imported by source files which do not belong to the library.

Suppose the library's header files are imported by main.cc, defined in the directory ~/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 the soft-links to its header files and module-compiled header files).

To make the library's headers available to main.cc two soft-links to relative destinations can be defined:

Once these links are available main.cc can be compiled using

    g++ -c --std=c++26 -fmodules-ts -isystem hdrs main.cc

25.3.2.2: Using an absolute location

When importing a local library's header using two soft-links to relative destinations it's also possible to define a single soft-link to the absolute location of the directory containing the library's headers. There's no compelling reason for using relative or absolute destinations. Maybe if the relative path specification becomes complex using the absolute destination is slightly more attractive.

Using the ~/project/main.cc from the previous section define a soft-link to the absolute location of the library's hdrs sub-directory in the project's gcm.cache sub-directory (note: here no comma is used):

    ln -s ~/support/locallib/hdrs gcm.cache
Next compile main.cc using
g++ -c -fmodules-ts -isystem ~/support/locallib/hdrs main.cc

25.3.3: Local header files

Although local header files can also be module-compiled doing so is deprecated as using (local) headers is probably a left-over from the traditional program development era.

But, for the sake of completeness, local header files can be module-compiled. But note that once a header file is compiled using namespace declarations specified in header files are lost when the header file is either included or imported. Compiled local header files are stored in the gcm.cache/,/ (note: a comma) sub-directory. They're automatically used when either their header files are included (e.g., #include "header.h") or imported (using import "header.h";).

25.4: Namespaces

Modules and namespaces are mutually independent. Modules (and/or partitions, cf. section 25.6) can define (contain) namespaces and/or they can define components in namespaces. Source files importing such modules must either explicitly specify the namespace of such components or they can specify a using namespace declarations avoiding the explicit namespace specification when referring to components living in those namespaces.

Here is a simple example of a module exporting a variable which is defined in a 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 namespace completely covering the module's export compound. Variations are possible: a namespace could completely surround the export compund, a namespace could be defined inside the export compound, only some of the module's components could belong to a namespace, non-exported components could belong to a namespace, multiple namespaces could be used, etc., etc..

To use the variable g_count, its module is imported and its namespace is specified 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 some using namespace declarations 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:

25.5: Templates

Templates are recipes used by the compiler to construct code tailored to specific types. Since they're recipes they cannot be compiled to running code. Traditionally templates are implemented in header files, and when used the compiler instantiates templates with the correct types using the templates' recipes.

Since modules are defined in source files there are no header files anymore when using modules. Modules specify their components in source files, replacing the traditional header files: by defining templates in a module interface unit the template's recipe character is kept, and they are instantiated when needed.

Here's an initial example. A module Adder exports a function template add 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 importing Adder 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

25.5.1: Class templates

In the previous section a function template was defined in a module interface unit. This section covers the definition of a class template. The class template SortMap offers the facilities of an unordered_map, but also has two sort members. Both return a vector with pointers to the elements of the unordered map. One member returns pointers to the elements sorted by their key values, the other member receives a functor, returning a vector of pointers to the elements which are sorted according to the functor's decisions.

The module interface unit defines the SortMap module. It declares the class template SortMap. The module, like any module, can be imported in other source files, like a source file main.cc. The main function inserts its arguments into SortMap 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 calling a.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 files containing class templates there's only one source file defining the module interface unit: it contains the class template's interface and also the implementations of its members. There is, however, an escape route: it's covered in section 25.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 interface unit, as it must contain the full implementation of the class template, may quickly grow to a very large file, which is hard to maintain. To simplify maintenance it is advised to adopt here the one function, one file design principle 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 the class SortMap itself is standard. At the top of the module interface unit the SortMap module name is defined followed by importing some module compiled system header files. Since algorithm is only used internally by the member functions it's not exported. But imported components must be specified at the top of module source files, and so algorithm cannot be imported by, e.g., sortmap.f.

The file sortmap.f itself merely includes the files implementing the two sort members, keeping the module interface unit as clean as possible. Here is sortmap.f:

    #include "sort1.f"
    #include "sort2.f"

Each of its included files contain the definition of one member function. A comment in the class's interface indicates which function is defined in which file. Here's the first sort 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 second sort 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;
    }

25.6: Partitions

In the introduction section of this chapter it was stated that modules offer new, conceptual features: modules offer program sections which are completely separated from the rest of the program, but modules themselves can define subsections (called partitions) which can define components (classes, types, variables, etc.) which are only accessible to the module and its partitions.

Within the restriction that partitions can be defined so that they're only accessible to their module and their sibling-partitions the access rights of classes defined in partitions are identical to those of classes in general: public members are accessible to their users, protected members are available to classes derived from those classes, while private members cannot be accessed from outside of those classes.

Figure 36: Partitions of the `Math' module

Figure 36 illustrates a simple module (Math) having two partitions. It shows the relationships between the module and its partitions in 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 in the middle, and the module itself, depending on both partitions, is at the bottom. The Math module defines a class Math, having a member returning the sum of two size_t values and a member count returning the number of performed additions. It's a very basic design merely illustrating the way partitions are designed.

The object of class Add performs an addition, and the object of class Utility keeps track of the number of additions that were performed. Both Add and Utility are defined as classes in their own partitions: Math:Utility and Math:Add.

Note that partitions themselves are not classes. When defining a partition interface unit only a single colon is used instead of two, as used then defining cass member functions (so Math:Utility and not Mat::Utility).

The Math interface unit exports class Math, allowing code which does not belong to the module Math itself to define Math objects after importing the Math module:

    import Math;
    void fun()
    {
        Math math;
        ...
    }

Since the class Math has data members Utility d_util and Add d_add these classes must be known inside the Math module interface unit (modmath.cc), but also by software defining objects of class Math (comparable to the requirement to, e.g., include <string> when a class defines a std::string data member). Therefore the Math module interface unit specifies export import for the Utility and Add partitions. As we'll shortly see these export specifications do not imply that software merely using the facilities of the Math module can also define, e.g., Math:Utility objects: the partitions can still remain `private' to the Math module. Here is the Math module interface unit, defined in math/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 module Math defining partitions introduces a completely new level of separation: the components of partitions are like nested classes but don't result in `class overpopulation': partitions are not defined inside modules but are completely separately defined from their modules, while making their facilities only available to their modules and sibling partitions. And although probably not illustrating good design: by defining export compounds in partition interface units the elements in those compounds become directly available to using software essentially turning the partition into a module.

25.6.1: The Math:Utility partition

The Math:Utility partition is the most basic of the two Math partitions. It does not depend on features of either the Math module or the Math:Add partition. Since it does use the size_t type, it specifies export import <cstddef>. Here, because of its export specification, the definitions in cstddef are also available when Utility is imported, and since the Math module itself exports :Utility also to software importing Math.

As Utility is a Math partition its partition interface unit starts by specifing this:

    export module Math:Utility;
The colon indicates that this is not a plain module but a partition (note that the first line must start by specifying export). It contains only non-exported components, but as it's a partition of a module, all the partition's components are fully available to its module and its sibling partitions. Its class Utility has two simple members, both very simple, never changing one-liners. Because of that they're implemented in-line. Here's the Utility 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, its constructor could very well also have been defined inline, but (for illustration purposes) is defined in a separate source file. As Utility's constructor is a partition source file it starts by specifying its module (Math), followed by importing the partition component (:Utility) which is defined in this source file:

    module Math;
    import :Utility;

    Utility::Utility()
    :
        d_count(0)
    {}

25.6.2: The Math:Add partition

Different from the Math:Utility partition the Math:Add partition does depend on another partition: it depends on Math:Utility. Like Utility's modutility.cc it starts its modadd.cc file by exporting its partition name. But then it imports :Utility, since that partition is used by Math:Add, making available all components of that partition.

Since :Utility already imports <sstddef> :Add can also use size_t. For :Add the :Utility partition wouldn't have to specify export in front of import <stddef> since all components of imported partitions are available to both the module and its partitions. But by using export import <cstddef> users of the Math module automatically receive <cstddef>'s definitions (among which size_t). Here's the Math::Add partition interface unit (again: notice that the interface doesn't contain an export 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 also have been possible). Instead all members were defined in separate source files. Here is class Add's constructor:

    module Math;
    import :Add;

    // export import :Utility;  OK, but superfluous

    Add::Add(Utility &utility)
    :
        d_utility(utility)
    {}

and its sum member:

    module Math;
    import :Add;

    size_t Add::sum(size_t lhs, size_t rhs)
    {
        d_utility.inc();
        return lhs + rhs;
    }

25.6.3: The Math module's members

Now that the interfaces of the Math module and its partitions have been defined (cf. figure 36) the Math module's members can be defined. The constructor of class Add in the Math:Add partition needs a reference to a Utility object which it receives from Math constructor:
    #include "math.i"
    //import Math;

    Math::Math()
    :
        d_add(d_util)
    {}

Since class Utility's count member is public, it can be called by the Math::count member:

    import Math;

    size_t Math::count() const
    {
        return d_util.count();
    }

and finally, Math's add member calls Add's sum member, defined in the Math: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);
    }

25.6.4: Using the Math module

Now that the Math module has been developed it can be used by a program. The exported facilities offered by a module are available after importing the module. Note that although a module may export import its partitions only partition components defined in their export compound are available to source files not belonging to the module itself. As Math:Utility exports <cstddef> source files importing Math automatically also can use cstddef's features.

Here's a main function importing Math and using its facilities to add two positive integral numbers. Header files are not used anymore, and the implementation of the main function is equal to the implementation when using header files. However, compared to the latter implementation the compilation of a program using modules requires significantly less time, and the design of modules using partitions may offer better isolation of components that are mere building blocks of the those modules.

Here is the implementation of the main function using the Math 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';
    }

25.6.5: Partitioned class templates

In section 25.5.1 the a class template SortMap was defined in a module interface unit. Like class templates in header files class templates in a module interface files may grow large because the file not only contains the class template's interface but also the implementations of its members. Consequently a full recompilation is required when only a single member function is modified. Recompilations cannot completely be avoided, but by using module partitions complete recompilation can often be avoided.

Some maintenance complications, however, remain: once a class template's member is modified the modification is not visible in members that use that member. But this dependency issue always plays a role when using templates: once a template has been modified all code using the template needs to be recompiled.

In the current section class templates are implemented using module partitions, each containing a member of the class template. The previously developed SortMap module consists of three components: the class interface and two sort members functions. Now when using partitions each component will be defined in its own partition, and the module itself contains export import statements for each partition which must be accessible by code using the module. Consequently the modsortmap.cc module interface unit 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 be defined in the module interface unit itself, since partitions can depend on other partitions, but not on the module itself as the partitions must (as they are imported by the module) be available before the module itself can be compiled. Partitions, however, can depend on other partitions, so a partition implementing a sort function can import a partition providing the class's interface.

As the :Interface partition merely specifies what's offered by the class SortMap it doesn't depend on the other partitions. So it merely declares but does not contain the implementations of the sort members, and thus it does not itself have to import the alorithm module-compiled header:

    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 two sort members do depend on the interface, since they implement their declarations. They also depend on the facilities provided by the algorithm module-compiled header. Both are therefore imported. Here's the implementation of the first sort 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 second sort 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;
        }
    }

The main function can remain as provided in section 25.5.1. When constructing the program the partition dependencies must be taken into account:

25.7: Module mapping

Modules cannot mutually import each other. Consequently there's always a linear, non-circular module hierarchy, which should be acknowledged when compiling files defining and using modules. In this section the requirements for projects using modules are covered, as well as a procedure that can be adopted when constructing such projects.

With modules traditional headers (.h files) should be avoided. System header files (cf. section 25.3.1) still exist, but for those import statements should be used or made available. For locally developed libraries module-compiled header files can also be used (cf. section 25.3.2).

It can be difficult to determine which module sections can immediately be compiled, and which ones depend on already compiled sections. Moreover, source files belonging to modules but not defining their interface units may import modules which aren't yet available when compiling the module's interface unit. For example, module Basic's interface unit is independent of other modules:

    export module Basic;

    export
    {
        class Basic
        {
            public:
                void hello() const;
         };
    }

But a module Second imports Basic and therefore the compilation of its module interface file depends on the availability of gcm.cache/Basic.gcm:

    export module Second;

    export import Basic;
    export import <iosfwd>;

    export
    {
        class Second
        {
            public:
                static std::string hello();
        };
    }

Consequently basic/modbasic.cc must be compiled before compiling second/modsecond.cc. However, module Second may export a class Second, and an object of class Second could be defined by a member of the class Basic, exported by the Basic 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 right order, be compiled. Then, once they are available the remaining source files of the modules can be compiled.

This two-step process (first compile the module interface units in the right order and then compile the remaining source files) quickly becomes a challenge, which is commonly handled by using a module mapper. Module mappers inspect the modules of a project, building their dependency tree, and maybe compiling the module interface units in the right order. The module mapper icmodmap(1), a utility program of the icmake(1) project, is such a module mapper.

By default (but configurable via options) icmodmap(1) expect projects using modules to be organized as follows:

Icmodmap(1) inspects each of the source files in the project's top-level directory and in each sub-directory specified in the CLASSES file. If a source file's first line starts with export module it's a module or partition interface unit. If so, its module or partition name is stored in a data-base. Since module and partition interface units may themselves import modules and/or partitions the current interface depends on those imported components. Those imported components are declared in its data-base registering that the current interface unit depends on those imported components.

Likewise, if another source file imports modules or implements a component of a module or partition then those modules (and/or paritions) are also declared in its data-base.

Once all source files were inspected icmodmap(1) determines the module dependencies: its data-base must indicate that each imported module or partition also has a module/partition interface unit, and that there are no circular dependencies among the module/partition interface units. If these requirement are satified then icmodmap by default calls the compiler to compile the interface units in their proper order. By default the object files are stored in the standard icmake(1) fashion: each compiled interface unit is stored in the project's tmp/o sub-directory and their names begin with a number which is equal to the line number in the CLASSES file specifying the inspected sub-directory (like 1modfirst.o, 2modsecond.o,) using prefix 0 for module(s) defined in the project's top-level directory.

When the inspection and compilation successfully completes then the interface units' object files are stoed in tmp/o, and the project's top-level directory contains a sub-directory gcm.cache containing the .gcm files of all modules and partitions. In addition each inspected sub-directory has received a soft-link gcm.cache to its parent's gcm.cache sub-directory, so each of the project's source files can import each module.

Now that the module and parition interface files are available the remaining source files can be compiled as usual. They can import and use the components of the the defined modules (and where applicable the defined paritions).

25.7.1: Modifying interface units

When module/partition interface units are modified components are added, removed, or altered. E.g., an interface unit may declare a class which is modified: the ordering of its data members may change, new data members may be added, existing data members may be removed.

When a module interface unit is modified and it's recompiled then the modified definition replaces the old one. Consider this interface unit:

    export module Demo;
    export
    {
        class Demo
        {
            int d_value = 100;
            public:
                int value() const;
        };
    }
The member function Demo::value (returning d_value) is implemented in its own source file, and is called by main:
    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 is modified: 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;
        };
    }

Next moddemo.cc and main.cc, are recompiled and the three object files are linked constructing the binary program. The new binary outputs 13: value's object file returns the first bytes of the Demo object as an int, but since the class Demo was modified those bytes no longer contain the value 100.

Such complications are familiar: when using a traditional header declaring a class and the class changes the organization of its data members then in practice all source files using the class must be recompiled (and recursively: if another class is derived from, or contains a data member of the modified class then all source files using the other class must also be recompiled). When using modules such recompilations are also required. The icmodmap(1) support program has an option to recompile module (partition) interface units depending on modified interface units and either to modify the last write times of source files using those modules or to write their names to a file so a build utility can recompile those module using source files.

25.7.2: External modules

Modules can import module-compiled system header files (cf. section 25.3.1) and/or module-compiled header files of locally developed libraties (cf. section 25.3.2).

What if a library's design merely uses modules? In that case the library must make available its module-compiled interface files (i.e., .gcm files) to its users. Copying the library's .gcm files to the gcm.cache sub-directory of a using project is not necessary (and probably a bad design anyway, since modifications of the library's .gcm files will not be noted), but soft-links should be used. However, often modules import other modules or partitions, and consequently such library-modules in turn may depend on other modules (or partitions). When a project must import a library's module not only the module's .gcm file must be available but also (recursively) the module/partition .gcm files that module depends on.

As an illustration consider the situation where a project defines three modules: Module1, Module2, and Module3. Module1 imports External, which is a module offered by some library; Module2 imports Module1, and Module3 imports Module2 (cf. figure 37).

Figure 37: Three modules and an external module

The External module doesn't belong to the current project, and so the External.gcm file lives elsewhere. When constructing the project its gcm.cache directory must make External.gcm available. This is realized by defining a soft-link to the actual location of External.gcm. But in addition soft-links to the modules/partitions imported by External.gcm must be made available in the project's gcm.cache sub-directory.

The icmodmap(1) support program can be used to determine and to satisfy the requirements of externally defined modules. To determine the modules' dependencies icmodmap is called in the project's top-level directory specifying its --dependencies (or -d) option. It shows the dependencies amount the modules and reports an error as the module External 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 Module2
And indeed, there's an error: the External module wasn't developed in the context of the current project. It's defined in an external library, offering its module/partition compiled interface units to its users. By informing icmodmap where the external .gcm files are located the error is solved. The relative or absolute path to the directory containing the library's .gcm files is either specified as a command-line option or it's specified in a file. Either of these can be passed to icmodmap using its --extern (or -e) option. E.g., if the library's .gcm files are in /tmp/library/gcm.cache then by calling
    icmodmap -d -e /tmp/library/gcm.cache
the 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 Module2
specifying --extern also defines the soft-links to the external modules: the project's gcm.cache sub-directory now contains the soft-link
    External.gcm -> /tmp/library/gcm.cache/External.gcm
Now that the requirements of the project's module interface files are all satisfied 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 used object files of the external library in the usual way (specifying, e.g., the linker's -L and -l options).

25.8: Design

When designing projects using modules the same approach as used with traditional projects (using headers) can be followed. Some additional conventions are suggested that specifically apply to modules. Filenames are just filenames. In the C++ Annotations following conventions are adopted:

The compiler writes compiled interface units in their gcm.cache sub-directories. The .gcm filenames of modules are equal to the names of the modules (e.g., for Group it is Group.gcm). The filename of a module-compiled partition starts with its module name, followed by a dash (-), followed by the partitions's name (e.g., for Group:Support it is Group-Support.gcm).

To avoid overfilling interface units they should (like class headers) not define components but merely declare them. Their definitions are provided in separate source files, defined in their interface unit's sub-directory, using the familiar the one component, one source file design principle.

25.8.1: 'header' and 'frame' files

Modules and partitions usually their components in separate source files. The initial lines of those source files are identical: they specify the module or the partition to which they belong. Since these source files do not include (internal) header files, they may also declare namespaces. E.g., the member implementations of the Math:Add partition (cf. section 25.6) all start with
    module Math;
    import :Add;
Since the Math:Add partition interface unit declares a class Add most or all of its source files implement a member of class Add. In these cases it's convenient to have a predefined frame file containing the essential elements of its source files. E.g., for Math::Add sources this could be
    module Math;
    import :Add;

    using namespace std;

    void Add::()
    {
    }
When implementing a member copy the frame to the intended source file, specify the member's name, define its body and maybe change its return type.