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

Contents[Show]

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.

 TimelineCpp20

You may wonder, why I don't complete my promised post about variadic templates. The reason is simple. My next pdf-bundle that 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 use in this post intentionally the newest Visual Studio compiler. Because its C++20 modules support is almost complete. The newest GCC and Clang only partially support modules.

private Module Fragment

I'm not sure if you have the facts about the module interface unit and the module implementation unit ready? Therefore, let me repeat the important 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

 

// 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.

For the Microsoft compiler, you have to specify the exception handling model (/EHsc).  Additionally, use the flag /std:c++latest.

Finally, here is the output of the program:

 client4

 

One of the big advantages to structure 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") that 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 out of the import directive and treats the result as if it would be 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 exported names are 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 that all standard library headers are importable headers. The ability to import excludes C headers.

What's Next?

In my next post, I 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, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, and Robin Furness.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, and Said Mert Turkal.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 
 

 

 

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 254

Yesterday 8036

Week 16469

Month 191179

All 7253469

Currently are 186 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments