C++20 Modules: Private Module Fragment and Header Units

In the last weeks, I learned something new about modules in C++20: private modules fragments and header units. Consequently, I make a short detour in this post and present these new features.

 

You may wonder why I didn’t complete my promised post about variadic templates. The reason is simple. The next pdf-bundle I publish next week is about C++20 modules, and I want to incorporate this post in this bundle. Before I do that, I have to write this post.

Private module fragments and header units make dealing with modules in C++20 way more comfortable.

I intentionally use the newest Visual Studio compiler in this post because its C++20 modules support is almost complete. The newest GCC and Clang only partially support modules.

private Module Fragment

I’m unsure if you have the facts about the module interface unit and implementation unit ready. Therefore, let me repeat the essential facts.

When you want to separate your module into an interface and an implementation, you should structure it into a module interface unit and one or more module implementation units.

Module Interface Unit

// mathInterfaceUnit2.ixx

module;                   

#include <vector>                           

export module math;       

export namespace math {

    int add(int fir, int sec);
 
    int getProduct(const std::vector<int>& vec);

}
  • The module interface unit contains the exporting module declaration: export module math.
  • The names add, and getProduct are exported.
  • A module can have only one module interface unit.

Module Implementation Unit

 

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.

     

    // mathImplementationUnit2.cpp
    
    module math;
    
    #include <numeric>
    
    namespace math {
    
        int add(int fir, int sec){
            return fir + sec;
        }
    
        int getProduct(const std::vector<int>& vec) {
            return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
        }
    }
    
    • The module implementation unit contains non-exporting module declarations: module math;
    • A module can have more than one module implementation unit.

    Main Program

    // client4.cpp
    
    #include <iostream>
    #include <vector>
    
    import math;
    
    int main() {
        
        std::cout << std::endl;   
       
        std::cout << "math::add(2000, 20): " << math::add(2000, 20) << std::endl;
        
        std::vector<int> myVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        
        std::cout << "math::getProduct(myVec): " << math::getProduct(myVec) << std::endl;
        
        std::cout << std::endl;
       
    }
    
    •  From the user’s perspective, only the namespace math was added

    Building the Executable

    Manually building the executable includes a few steps.

    cl.exe /std:c++latest /c mathInterfaceUnit2.ixx /EHsc                // (1)
    cl.exe /std:c++latest /c mathImplementationUnit2.cpp /EHsc            // (2)
    cl.exe /std:c++latest /c client4.cpp /EHsc                           // (3)
    cl.exe client4.obj mathInterfaceUnit2.obj mathImplementationUnit2.obj   // (4)
    
    1. Creates the object file mathInterfaceUnit2.obj and the module interface file math.ifc.
    2. Creates the object file mathImplementationUnit2.obj.
    3. Creates the object file client4.obj.
    4. Creates the executable client4.exe.

    You should specify the exception handling model (/EHsc) for the Microsoft compiler.  Additionally, use the flag /std:c++latest.

    Finally, here is the output of the program:

    client4

    One of the significant advantages of structuring modules into a module interface unit and one or more module implementation units is that modifications in the module implementation units do not affect the module interface unit and, therefore, require no recompilation.

    Private Module Fragment

    Thanks to a private module fragment, you can implement a module in one file and declare its last part as its implementation using module :private;. Consequently, a modification of the private module fragment does not cause recompilation. The following module declaration file mathInterfaceUnit3.ixx refactors the module interface unit mathInterfaceUnit2.ixx and the module implementation unit mathImplementationUnit2.cpp into one file.

    // mathInterfaceUnit3.ixx
    
    module;                   
    
    #include <numeric>
    #include <vector>
    
    export module math;       
    
    export namespace math {
    
       int add(int fir, int sec);
    
       int getProduct(const std::vector<int>& vec);
    
    }
    
    module :private;               // (1)
    
    int add(int fir, int sec) {
        return fir + sec;
    }
    
    int getProduct(const std::vector<int>& vec) {
        return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
    }
    

    module: private; (line 1) denotes the start of the private module fragment. A modification in this optional last part of a module declaration file does not cause its recompilation.

    I already presented header units in a previous post. Now, I can use them

    Header Units

    Header units are a smooth way to transition from headers to modules. You have to replace the #include directive with the new import statement.

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

    First, import respects the same lookup rules as include. This means in the case of the quotes (“myHeader.h”), the lookup first searches in the local directory before it continues with the system search path.

    Second, this is way more than text replacement. In this case, the compiler generates something module-like from the import directive and treats the result as a module. The importing module statement gets all exportable names for the header. The exportable names include macros. Importing these synthesized header units is faster and comparable in speed to precompiled headers.

    Modules are not Precompiled Header

    Precompiled headers are a non-standardized way to compile headers in an intermediate form that is faster to process for the compiler. The Microsoft compiler uses the extension .pch , and the GCC compiler .gch for precompiled headers. The main difference between precompiled headers and modules is that modules can selectively export names. Only in a module are exported names visible outside the module.

    After this short remainder, let me try it out.

    Use of Header Units

    The following example consists of three files. The header file head.h, declaring the function hello, its implementation file head.cpp, defining the function hello, and the client file helloWorld3.cpp using the function hello.

    // head.h
    
    #include <iostream>
    
    void hello();
    

    Only the implementation file head.cpp and the client file helloWorld3.cpp are special. They import the header file head.h: import "head.h";.

    // head.cpp
    
    import "head.h";
    
    void hello() {
    
        std::cout << '\n';
    
        std::cout << "Hello World: header units\n";
    
        std::cout << '\n';
    
    }
    

    // helloWorld3.cpp
    
    import "head.h";
    
    int main() {
    
        hello();
    
    }
    

    These are the necessary step 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 (first line) causes the creation of the ifc file head.h.ifc from the header file head.h. The ifc file contains the metadata description of the module interface.
    • The implementation file head.cpp (second line) and the client file helloWordl3.cpp (third line) use the header unit. The flag /headerUnit head.h=head.h.ifc imports the header and tells the compiler or linker the name of the ifc file for the specified header.

    helloWorld3

    There is one drawback with header units. Not all headers are importable. Which headers are importable is implementation-defined, but the C++ standard guarantees all standard library headers are importable. The ability to import excludes C headers.

    What’s Next?

    In my next post, I will use variadic templates to implement the C++ idiom for a fully generic factory. One implementation of this life-saving C++ idiom is std::make_unique.

    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,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *