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 45 MB: a bit more than their preprocessed 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. 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: An initial, complete module

Modules (and partitions, cf. section 25.5) are designed using source files instead of header files. A module always defines a module interface. The module's interface 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 `module.cc', 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;

    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)
        };
    }

    double g_squared;
This module interface merely serves as an illustration. In practice module interfaces don't contain so 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 shown.

The interface's top-line exports and defines the module name. This must be a line by itself. Next, the function square and class Square are declared inside an export compound. Components can also individually exported, but using an `export component' is convenient. Exported components can be used outside of the module. Components that are not exported (like g_squared) are only available to the components of the module itself, 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 double g_squared: it is therefore a definition, not a declaration. To merely declare variables use extern (as in extern double g_squared), but in that case another module-specific source file defining g_squared is required.

This module.cc file can now be compiled:

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

Compilation of the module.cc file not only produces the file interface.o, but also results in 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 that implement components of the Square module, and it must also be available to other source files that import the Square module: the directories containing such files must therefore also have gcm.cache subd-directories containing the Square.gcm file. This requirement is complex, and in practice a so-called modmapper (cf. section 25.6) is used to handle this complexity. In practice the requirement can often boil down to defining a top-level directory gcm.cache and defining a soft-link square/gcm.cache to the top-level's gcm.cache directory before compiling square/module.cc resulting in the following directory structure:

    .
    +-- gcm.cache
    |   +-- Square.gcm
    |
    +-- square
        +-- gcm.cache -> ../gcm.cache
Using this structure the Square.gcm module compiled interface file is available to all of the program's source files.

All components of the module Square must specify that they're part of that module. Traditionally (internal) header files were used. But including header files should no longer be required and should therefore be avoided when defining and using modules. Instead of the (traditionally used) square.h and square.ih files it is advised to use a frame file, specifying the requirements for compiling source files. 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 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.

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 at the top-level directory, 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 the Square module. It also imports <iostream> (cf. section 25.3) so the program can interact with its user:

    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 still be constructed as we're used to, by linking the object files, resulting in a binary program.

This example illustrates several characteristics of modules:

25.2: Namespaces

Modules and namespaces are mutually independent constructs. Modules 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 (which is independent from the module's name) or they must specify a using namespace declaration 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
    {
        size_t g_count;
    }
It's a very simple example, but it shows the essence of defining a namespace inside a module definition. In practice a class would probably be specified inside the namespace.

To use the variable g_count, its module must be imported and its namespace must be specified. E.g.,

    import NS;
    import <iostream>;

    int main()
    {
        ++FBB::g_count;

        std::cout << FBB::g_count << '\n';
    }

    // alternatively:
    //
    // import NS;
    // import <iostream>;
    //
    // using namespace std;
    // using namespace FBB;
    //
    // int main()
    // {
    //     ++g_count;
    //     cout << g_count << '\n';
    // }

Some notes:

25.3: Module header files

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

And that's maybe understandable considering that merely reading the iostream and string system headers forces the compiler to process some 900,000 bytes. Traditionally using precompiling headers was an attractive alternative improving compilation speed, but precompiled headers are often really big: precompiling iostream results in iostream.gch, a file exceeding 45 MB. When using the importable alternatives there's at least a large reduction in file sizes compared to using precompiled headers. The importable iostream file is some 9 MB large, the importable string file some 5 MB.

To avoid recompiling system header files for different projects consider storing the compiled headers in /usr/include/c++/14 (here, '14' is g++'s main version number: update it to your actually installed version). To make system headers available to modules define the soft-link usr -> /usr in the gcm.cache sub-directory of source files importing system header files.

To compile system headers so that they're stored in /usr/include/c++/14 define a /usr/include/c++/14/gcm.cache sub-directory, containing the same soft-link: usr -> /usr. Then, to compile (e.g., iostream) execute

    g++ --std=c++26 -fmodules-ts -x c++-system-header iostream
producing the file /usr/include/c++/14/gcm.cache/iostream.gcm, and then move gcm.cache/iostream.gcm to the current directory. Source files can then do `import <iostream>;' to use the iostream facilities.

To summarize:

Note: currently (g++ version 14.2.0) you may encounter compilation sequence issues when module-compiling system headers. For some system headers compiling them 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 compilation of the system header file, and then moving the tmp/*.gcm files back to the current directory.

25.3.1: Local header files

Although local header files can also be module-compiled doing so should be avoided as doing so is probably a left-over from the traditional program development era.

In the introductory paragraphs of this chapter it was already mentioned that, when using modules, using a frame file is preferred over using (internal) local header files: source files importing modules or defining module components are all plain source files, and therefore, since the frame file imports all required module components it's easy to define another component or another source file using modules. It's of course possible that at some point, during the implementation of such sources additional imports are required. In those cases simply add the additional imports to the frame file. It doesn't invalidate the already defined source files and simplifies defining new source files.

But, for the sake of completeness, local header files can also be (module-)compiled. However, 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: Templates

Templates are recipes used by the compiler when applicable. 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.

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

As a very basic illustration consider the following module interface source file exporting a template:

    export module Template;

    export
    {
        template <typename Type>
        Type add(Type const &t1, Type const &t2)
        {
            return t1 + t2;
        }
    }

When the Template module is imported in a source file the add template can be instantiated for several types:

    import Template;

    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.4.1: Templates in classes

The previous section illustrates the basic design of defining a template in a module. In this section a more realistic example is covered. Section 11.11.1 covered the construction of standard ostream manipulator insertion operators. In addition to those manipulators all kinds of types can be inserted into ostreams. Since all possible insertable types were unkown when std::ostream was implemented, insertion of possible types is handled by a template.

In this section that same approach is adopted to define a module Error exporting a class Error offering insertion operators. An error message is considered complete once a std::endl is inserted into the Error object, and the class also offers a member count returning the number of error messages that have been inserted into the object. The inserted messages are also written to std::cerr.

The module.cc file of the Error module is

     1: export module Error;
     2: 
     3: export import <iostream>;
     4: 
     5: export
     6: {
     7:     class Error
     8:     {
     9:         template <typename Type>
    10:         friend Error &operator<<(Error &error, Type const &type);
    11: 
    12:         friend Error &operator<<(Error &error,                      // 2
    13:                                  std::ios_base &(*func)(std::ios_base &));
    14: 
    15:         friend Error &operator<<(Error &error,                      // 3
    16:                                  std::ostream &(*func)(std::ostream &));
    17: 
    18:         size_t d_count;
    19: 
    20:         public:
    21:             Error();
    22:             size_t count() const;
    23:     };
    24: }
    25: 
    26: template <typename Type>
    27: Error &operator<<(Error &error, Type const &type)
    28: {
    29:     std::cerr << type;
    30:     return error;
    31: }
    32: 
    33: inline Error::Error()
    34: :
    35:     d_count(0)
    36: {}
    37: 
    38: inline size_t Error::count() const
    39: {
    40:     return d_count;
    41: }

The second and third insertion operators are implemented as free functions; both returning their left-hand side (Error) operands. Both implementations are no different from the implementations that traditionally would have been used. Here's the second insertion operator:

    module Error;
    using namespace std;

    Error &operator<<(Error &error, ios_base &(*func)(ios_base &))
    {
        cerr << func;
        return error;
    }

The third insertion operator also increments error.d_count when std::endl is inserted:

    module Error;
    using namespace std;

    Error &operator<<(Error &error, ostream &(*func)(ostream &))
    {
        if (ostream &(*endMsg)(ostream &) = endl; func == endMsg)
            ++error.d_count;

        cerr << func;
        return error;
    }

Once module.cc has been compiled and the gcm.cache components are available to a using program that program can use the Error class. Here is a main function doing so:

    import Error;
    using namespace std;

    int main()
    {
        Error error;

        error << "hello " << 12 << '\n';
        cout << "Number of errors: " << error.count() << '\n';

        error << "after inserting std::endl: " << endl;
        cout << "Number of errors: " << error.count() << '\n';
    }

Running the program produces the following output:

    hello 12
    Number of errors: 0
    after inserting std::endl: 
    Number of errors: 1

25.5: Module 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 may define data and types (like classes) which are only accessible to the module and its partitions.

If such partitions define classes then the access rights of the members of those classes are not altered: the public members of those classes are accessible to the members of the module and its partition, their protected members are available to classes derived from those classes, defined in the module or its partitions, while their private members are not available outside of the class.

Figure 35: Partitions of the `Math' module

Figure 35 illustrates a simple module (Math) having two partitions. It shows the relationships between the module and its partitions in the same way as the relationships between classes are commonly displayed: the most basic (least dependent) partition is on top, the partition depending on the topmost partition is in the middle, and the module depending on both partitions is at the bottom. The Math module defines a class Math, defining a member returning the sum of two size_t values and a member count returning the number of performed additions. It's a very simple design, illustrating the way partitions are designed. The class Add object performs the addition, and the class Utility object 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 are not class members: partitions are specified using only a single colon instead of two, as used then defining cass member functions.

The Math module and its Math class are exported, so the class Math can be used by other source files after importing the module Math:

    import Math;
On the other hand, Math:Add and Math:Utility are partitions, and components of partitions cannot be imported by sources not belonging to the Math module or its partitions. Partitions, therefore, allow the implementation of partially private components: not accessible outside of the module, but accessible all over the module itself.

The Math module itself exports all its components, including its partitions, since they're all used by the exported Math class. Note that when importing partitions their (Math) module name isn't specified: since partitions can only be defined within the context of a module the module name is implied, and partitions are imported without mentioning their module names:

    export import :Utility;

Other than importing its partitions the Math module's interface contains no other specific details, but it `exports imports' the <cstddef> module header file since the Math class refers to the size_t type. Here is its module.cc file:

    export module Math;

    export import <cstddef>;

    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);
        };
    }

What is important is that what's imported by a module must exist before the module's module.cc file can be compiled. So the partitions Utility and Add (specifically: their gcm.cache/*.gcm files) must be available before Math's module.cc file can be compiled. Once the required *.gcm files are available the remaining source files of partitions and modules can be compiled. In practice, a modmapper program (cf. section 25.6) is used to prepare the required gcm.cache files, whereafter the remaining source files can be compiled as usual.

Since (cf. figure 35) the Utility partition is the most basic, it is covered next.

25.5.1: The Math:Utility partition

The Math:Utility partition does not depend on features of either the Math module or the Math:Add partition. Since it does use the size_t type, it `exports imports' <cstddef>.

Even though it's a partition its module.cc file starts by exporting a module, not by, e.g., exporting a partition:

    export module Math:Utility;

The colon indicates that this is not a plain module but a partition. It exports a class Utility defining a size_t d_count data member, a constructor, and two simple members (in this example they're defined inline):

    export module Math:Utility;

    export import <cstddef>;

    export
    {
        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 is defined in a separate source file (cf. section 25.5.4 below).

25.5.2: The Math:Add partition

Different from the Math:Utility partition the Math:Add partition does depend on another partition: it depends on the Math:Utility partition. Like Utility's module.cc it starts its module.cc file by exporting its partition name, followed by importing the components required by the Math:Add partition. The elements of the partition (in this case: the class Add) can now be exported:
    export module Math:Add;

    export import :Utility;
    export import <cstddef>;

    export
    {
        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 (cf. section 25.5.4 below).

25.5.3: The Math module itself

Now that the interfaces of the Math module's partitions have been defined (cf. figure 35) the Math module's module.cc itself can be defined. Math itself is a mere module, but since it depends on its partitions it has to export import its partitions, in addition to exporting its own module name. Here is its module.cc file:
    export module Math;

    export import <cstddef>;

    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 Math module's class Math defines a Utility and and an Add object. Note that these are just class type objects, but they're defined by, respectively, the Math:Utility and Math:Add partitions. Next we'll cover the implementations of the class Math's members.

25.5.4: Compiling the remaining source files

Now that the interface files of the module and its partitions have been covered (and in practice: compiled), the source files of the module and its partitions can be compiled. To compile those files a build-utility can again be used.

Source files of partitions must first declare their module, followed by importing their partition. E.g., for the Utility partition this boils down to:

    module Math;
    import :Utility;
Following these lines other specifications may be provided, like importing additional components (e.g., `import <iostream>;') or declaring namespaces. Since modules shouldn't use #include directives, partitions don't need tailored (internal) header files. Instead, as covered before, when implementing a partition component using frame files are advised: when defining a new source file start by copying frame to the new source file, followed by completing its implementation. For Math:Utility the following frame file is used:
    module Math;
    import :Utility;
    //using namespace std;

    Utility::()
    {
    }

Beyond the required declaration and imports components of partitions (and modules) are implemented as usual. E.g., here is the implementation of Math:Utility's class Utility's constructor:

    module Math;
    import :Utility;

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

The Math:Add partition's remaining source files also start from a frame file:

    module Math;
    import :Add;    
    //using namespace std;

    Add::()
    {
    }

Since frame satisfies all requirements Add's members can now straightforwardly be implemented. Here's Add's constructor:

    module Math;
    import :Add;

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

and its sum member, after calling Uility::inc, simply returns the sum of its two parameter values:

    module Math;
    import :Add;

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

The Math module defines its own a frame file, which is used to start the definition of the members of its Math module:

    import Math;
    //using namespace std;

    Math::()
    {
    }

The Math module's class Math defines a constructor and two members, which can be defined as usual. Its constructor:

    import Math;

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

Its count member:

    import Math;

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

and its add member:

    import Math;

    size_t Math::add(size_t lhs, size_t rhs)
    {
        return d_add.sum(lhs, rhs);
    }

25.5.5: Using a module having partitions

A program using a module having partitions is defined like any program using modules. When a module depends on partitions then the gcm.cache sub-directory contains the .gcm files of the partitions as well as the module's own .gcm file. For the Math module that file is named Math.gcm, and its partitions have names like Math-Utility.gcm. If the program's gcm.cache sub-directory offers access to those .gcm files then the program's files can simply define and use Math class objects (see also section 25.6).

As with modules and partitions no (internal) header file is needed anymore at the program's top level. Instead a frame file is used importing and declaring what's used by the program level source files. Here is the frame file used by a program using a class Math object:

The program itself is simple: it merely contains a main function. Here is its definition:

    import Math;
    import <iostream>;

    using namespace std;

    int main()
    {
        Math math;

        cout << 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 count: " << math.count() << '\n';
    }

Once all sources (i.e., the program's source file, the module's source files, and the partition's source files) have been compiled, the gcm.cache files are not needed anymore, and the final program can be constructed (i.e., linked) as usual. If main.o is located in the program's main directory and if all object files are located in a tmp/o sub-directory, then the program can simply be constructed by requesting the compiler to perform the linkage:

    g++ main.o tmp/o/*.o

25.6: 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 a project.

When using modules using traditional headers (.h files) should be avoided. Projects not developed using modules may still have to #include headers, but except for those projects #include directives should no longer be used. System header files (cf. section 25.3) still exist, but for those import statements should be used or made available.

When designing a project it can be hard to determine which module sections can immediately be compiled, and which ones depend on already compiled sections. Moreover, source files belonging to modules which don't define the module's interface may need to import modules which aren't yet available when compiling the module's interface. For example, the interface of a module Basic is independent of other modules, as in

    export module Basic;

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

But the 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/module.cc must be compiled before second/module.cc can be compiled. However, module Second may offer a class Second, and an object of class Second may very well be used by a member of the Basic module, as illustrated by the implementation of basic/hello.cc:

    module Basic;

    import Second;
    import <iostream>;
    import <string>;

    using namespace std;

    void Basic::hello() const
    {
        cout << Second::hello() << '\n';
    }

In situations like these the modules' interfaces must be compiled first (and in the right order: the least dependent (Basic) one first, then the other (Second) one). Then, once the interface files were compiled the other source files of the modules can be compiled, even if a source file imports a module whose module.cc file had to be compiled after the module.cc file of the module to which the source file belongs.

this two-step process (first the modules' module.cc files in the right order and then the remaining source files) quickly becomes a challenge. To fight that challenge a module mapper is commonly used. Module mappers inspect the modules of a project, building their dependency tree. The module mapper icmodmap(1), a utility program of the icmake(1) project, can be used as a module mapper.

Icmodmap expect projects using modules as follows:

Icmodmap(1) inspects each specified sub-directory. If it contains the file module.cc (or its alternative name), then that file is inspected:

Once all module.cc files were successfully inspected icmodmap(1) determines the module dependencies, and if there are no circular dependencies the interface files are compiled in the required order. Each compiled interface file name will begin with an ordering number which is equal to the line number in the CLASSES file (or sub-directory order number of the inspected sub-directories), e.g., 1module.o, 2module.o, etc.

When icmodmap(1) successfully returns then all module defining interface files were successfully compiled, and the project's top-level directory contains a sub-directory gcm.cache containing the .gcm files of all modules and partitions; and each inspected sub-directory has received a soft-link gcm.cache to the top-level gcm.cache sub-directory, allowing the files in each sub-directory to import the project's modules. Next, the source files in the directories listed in the CLASSES file (and possibly the source files(s) in the top-level diretory itself) can be compiled as usual, e.g., using a build-utility.

25.7: Modules in libraries

Software libraries are traditionally constructed using header files. For example, the compiler's development header files are found in /usr/include/c++/14. Currently, only these header files are installed, and not their corresponding module-compiled .gcm equivalents. Section 25.3 covered how to construct the module-compiled versions of those system header files. Once the system header files were compiled and made available next to their traditional header files (so iostream and iostream.gcm are both available in the same directory) then module-aware source files can import those header files (i.e., import <iostream>;) instead of including them (as in #include <iostream>).

When constructing a library whose header files are available in a sub-directory of the standard include directory (like the headers of the bobcat library, cf. section 14.9), then the header files of such libraries are traditionally also available using #include directives like #include <bobcat/exception>. To prepare the headers of such libraries for use by module-aware source files is easy: simply construct their module-compiled versions like the way the module-compiled versions of the standard header files were constructed:

Since the system header files and their module-aware equivalents are located in the standard system files directories (all below /usr), the compiler will look for module-aware .gcm files either in the project's gcm.cache sub-directory or it will look for those files in the standard system directories, starting its search in the gcm.cache sub-directory. Hence, the icmodmap(1) module mapper, when defining a project's gcm.cache sub-directory, defines the soft-link usr -> /usr.

What if a library was designed merely using modules (so no traditional header files were used)? In that case, using the gcm.cache organization used by icmodmap(1), the modules' .gcm files are all available in the library's gcm.cache directory. When compiling source files of projects using the library's modules the .gcm files of the used library's modules must then be available in the project's gcm.cache sub-directory. Copying the .gcm files of the used library modules is not necessary, soft-links can also be used. But often modules import other modules, and so those modules depend on other modules, whose .gcm files must then also be available in the project's gcm.cache sub-directory. The icmodmap(1) option --dependencies can be used to determine the modules which are required by a module. When a (library) module is used then the .gcm files on which the module depends must also be available in the project's gcm.cache sub-directory.

As an illustration consider the situation where a library defines three modules: Module1, Module2, and Module3. Module1 imports External, which is a module defined in some other context; Module2 imports Module1, and Module3 imports Module2 (cf. figure 36).

Figure 36: Three modules and an external module

The External module doesn't belong to the library. The External.gcm file, therefore, lives elsewhere. So, when the library is constructed the library's gcm.cache directory must contain a soft-link (or copy) to the actual location of the External.gcm file.

Now, when a project uses modules from the library the project's gcm.cache sub-directory must contain soft-links to the used modules, but also soft-links to the modules used by the used module. The question which modules are actually required can be answered by icmodmap(1): it offers the --dependencies option showing which modules are required by each of the library's (or any project's, for that matter) modules. When executing `icmodmap -d .' in the library's base directory it shows:

    External:e
    Module1: External:e
    Module2: External:e Module1 
    Module3: External:e Module1  Module2
The :e which is appended to the External module name indicates that External.gcm is not part of the library itself, but is defined by some other project. The library's gcm.cache will therefore most likely offer a soft-link to External.gcm's real location. Furthermore, the output shows that a project importing Module2 not only must prepare soft-links in its gcm.cache sub-directory to the library's Module2.gcm file, but also to the library's External.gcm, and Module1.gcm files.

25.7.1: Locally developed libraries

In addition to libraries whose header files are located in (sub-directories of) the standard system include directories it's of course also possible that some a local library was constructed. That library could very well traditionally have been developed, using traditional header files.

To construct the module-aware versions of that library's header files the procedure described in the previous section can be used. However, since the header files of a local library aren't located in the standard system files directories, they must be made available in the gcm.cache sub-directory of the project using them.

It is possible to copy the headers and their module-compiled versions to the gcm.cache sub-directory, but a simpler alternative exists which can be used by multiple module-aware projects which may all depend on the local library.

First some preparatory steps are performed (only once):

Next, if a module-aware project must import module-aware variants of (some of) MyLib's header files,

When the local library was designed merely using modules, then a similar procedure can be used as described in the previous section for system libraries.