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
Modernes C++ Mentoring
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)
- Creates the object file mathInterfaceUnit2.obj and the module interface file math.ifc.
- Creates the object file mathImplementationUnit2.obj.
- Creates the object file client4.obj.
- 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:
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 filehead.h.ifc
from the header filehead.h
. The ifc file contains the metadata description of the module interface. - The implementation file
head.cpp
(second line) and the client filehelloWordl3.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.
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)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!