C++20: More Details about Module Support of the Big Three

In my last post, “C++20: Module Support of the Big Three“, I compiled a simple module with the Big Three. Today, I drill deeper using the GCC, Clang, and Microsoft Compiler.

Honestly, this post will be pretty technical and end with a curious note. Additionally, it requires basic modules knowledge. If you don’t have it, read my previous post about modules:

  1. The Advantages of Modules
  2. A Simple math Modul
  3. Module Interface Unit and Module Implementation Unit
  4. Structure Modules
  5. Open Questions to Modules
  6. Private Module Fragment and Header Units
  7. C++20: Module Support of the Big Three

Compiler Support of Modules

I use the Microsofts cl.exe 19.29.20133 for x64, the Clang 16.0.5, and the GCC 11.1.0 compiler for my experiments.

Microsoft Visual Compiler

Let me start with the Microsoft Compiler.

The Microsoft Visual Compiler provides various options for the using of modules.

Additionally, there are a few common cl.exe compiler options.

I use various compiler options for the ifc file in the following command lines. The ifc file is the module and contains the metadata description of the module interface.

  • Use the module math.cppm to create the obj and ifc file.
cl.exe /c /std:c++latest /interface /TP math.cppm
  • Use the module math.cppm to create only the ifc file.
cl.exe /c /std:c++latest /ifcOnly /interface /TP math.cppm
  • Use the module math.cppm to create the obj file math.obj and the ifc file mathematic.ifc.
cl.exe /c /std:c++latest /interface /TP math.cppm /ifcOutput mathematic.ifc
  • Create the executable client.exe and use the ifc file math.inter.
cl.exe /std:c++latest client.cpp math.obj /reference math.inter
  • Create the executable client.exe and explicitly use the ifc file math.inter that is in the directory ifcFiles.
cl.exe /std:c++latest client.cpp math.obj /ifcSearchDir ifcFiles /reference math.inter

Let’s continue with the Clang compiler.

Clang Compiler

The Clang compiler provides various options for the creation of modules.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    For more details, refer to the official Standard C++ Modules documentation. In the following command lines, I use the compiler options for the module and the ifc file.

    • Use the module declaration file math.cppm to create the pcm file math.pcm.
    clang++ -c -std=c++20 -fmodule-output math.cppm -o math.pcm
    
    • Use the module with the extension ixx (math.ixx) to create the pcm file math.pcm.
    clang++ -std=c++20 --precompile -x c++-module math.ixx -o math.pcm
    
    • Create the pcm file and use it.

    clang++ -std=c++20 -c math.pcm -o math.o
    clang++ -std=c++20 -fprebuilt-module-path=. math.o client.cpp -o client.exe
    
    • Use the pcm file other.pcm and compile it.
    clang++ -std=c++20 -c client.cpp -fmodule-file=math=other.pcm -o client.o
    

    Finally, here is the GCC compiler.

    GCC Compiler

    The following table shows the few GCC options.

    Many options of the Big Three are about header units.

    Header Units

    Header units are a binary representation of header files and represent a transition from headers to modules. You must replace the #include directive with the new import statement and add a semicolon (;).

    #include <vector>      => import <vector>;
    #include "myHeader.h"  => import "myHeader.h"; 
    

    For more information about header units, read my previous post “Private Module Fragment and Header Units“. In the following lines, I play with header units and use the following files:

    • The header file head.h
    // head.h
    
    #include <iostream>
    
    void hello();
    
    • The source file head.cpp importing the header unit
    // head.cpp
    
    import "head.h";
    
    void hello() {
    
        std::cout << '\n';
        std::cout << "Hello World: header units\n";
        std::cout << '\n';
    
    }
    
    • The main program helloWorld3.cpp importing the header unit
    // helloWorld3.cpp
    
    import "head.h";
    
    int main() {
    
        hello();
    
    }
    

    I will create a header unit from the header file head.h for the Microsoft Visual Compiler and the GCC Compiler. In contrast to the official documentation Standard C++ Modules, I could not master header units with the Clang Compiler.

    Microsoft Visual Compiler

    These are the necessary steps to use header units.

    cl.exe /std:c++latest /EHsc /exportHeader head.h
    cl.exe /c /std:c++latest /EHsc /headerUnit head.h=head.h.ifc head.cpp
    cl.exe /std:c++latest /EHsc /headerUnit head.h=head.h.ifc helloWorld3.cpp head.obj
    
    • The flag /exportHeader in line 1 causes the creation of the ifc file head.h.ifc from the header file head.h.
    • The implementation file head.cpp (line 2) and the client file helloWordl3.cpp (line 3) use the header unit. The flag /headerUnit head.h=head.h.ifc imports the header unit and tells the compiler the name of the ifc file for the specified header.

    GCC Compiler

    Creating and using the module consists of three steps.

     g++ -fmodules-ts -fmodule-header head.h -std=c++20
     g++ -fmodules-ts -c  -std=c++20 head.cpp 
     g++ -fmodules-ts  -std=c++20 head.o helloWorld3.cpp -o helloWorld3
    
    • Line 1 creates the module head.gcm. The flag -fmodule-header specifies the creation of a header unit from the header head.h.
    • The following line creates the object file head.o.
    • Finally, line 3 creates the executable that implicitly refers to the module head.gcm.

    As promised, here is a curious note.

    Reachability versus Visibility

    With modules, you have to distinguish between reachability and visibility. When a module exports some entity, an importing client can see and use it. Non-exported entities are not visible but may be reachable.

    // bar.cppm
    
    module;
    
    #include <iostream>
    
    export module bar;
    
    struct Foo {
        void writeName() {
            std::cout << "\nFoo\n";
        }
    
    };
    
    export struct Bar {
        Foo getFoo() {
            return Foo{};
        }
    };
    

    The module bar exports the class Bar. Bar is visible and reachable. On the contrary, Foo is not visible.

    // bar.cpp
    
    #include <utility>
    
    import bar;
    
    int main() {
    
        Bar b;
        // Foo f;                   // (1)          
        auto f = b.getFoo();   
        f.writeName();              // (2)
    
        using FooAlias = decltype(std::declval<Bar>().getFoo());   // (3)
        FooAlias f2;                // (4)
        f2.writeName();             // (5)
    
    }
    

    The class Foo is not exported and, therefore, not visible. Its usage in line (1) would cause a linker error. On the contrary, Foo is reachable because the member function getFoo (in bar.cppm) returns it. Consequentially, the function writeName (line 2) can be invoked. Furthermore, I can create a type alias to Foo (line 3), use it to instantiate Foo (line 4), and invoke writeName (line 5) on it. The expression std::declval<Bar>().getFoo() (line 3) returns the object that a call Bar.getFoo() would return. Finally, decltype returns the type of this hypothetical object.

    What’s Next?

    In my next post, I will dive deeper into the ranges library in C++20.

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,