Blog 4 min read

Why is the C++ modules feature so important for the future of C++?

Share this article
Why is the C++ modules feature so important for the future of C++?

C++ stagnated for many years, and many developers were convinced that the language would share the same fate as COBOL, Fortran, and VB6: no new projects would be developed with it, and C++ developers would simply maintain existing projects. But against all odds, C++ rose from the ashes, and the new standards significantly changed how the language is used.

However, the legacy #include mechanism is still there. After the modernization of the language, it became the next weak link that needed to be addressed. Indeed, it has many disadvantages; here are some of them, taken from this interesting document.

  • Compile-time scalability: Each time a header is included, the compiler must preprocess and parse the text in that header and every header it includes, transitively. This process must be repeated for every translation unit in the application, which involves a huge amount of redundant work. In a project with N translation units and M headers included in each translation unit, the compiler is performing M x N work even though most of the M headers are shared among multiple translation units. C++ is particularly affected because the compilation model for templates forces a huge amount of code into headers.
  • Fragility: #include directives are treated as textual inclusion by the preprocessor, and are therefore subject to any active macro definitions at the time of inclusion. If any active macro definition happens to collide with a name in the library, it can break the library API or cause compilation failures in the library header itself. For an extreme example, #define std "The C++ Standard" and then include a standard library header: the result is a horrific cascade of failures in the C++ Standard Library's implementation. More subtle real-world problems occur when the headers for two different libraries interact due to macro collisions, and users are forced to reorder #include directives or introduce #undef directives to break the (unintended) dependency.
  • Conventional workarounds: C programmers have adopted a number of conventions to work around the fragility of the C preprocessor model. Include guards, for example, are required for the vast majority of headers to ensure that multiple inclusion doesn't break the compile. Macro names are written with LONG_PREFIXED_UPPERCASE_IDENTIFIERS to avoid collisions, and some library/framework developers even use __underscored names in headers to avoid collisions with "normal" names that (by convention) shouldn't even be macros. These conventions are a barrier to entry for developers coming from non-C languages, are boilerplate for more experienced developers, and make our headers far uglier than they should be.
  • Tool confusion: In a C-based language, it is hard to build tools that work well with software libraries, because the boundaries of the libraries are not clear. Which headers belong to a particular library, and in what order should those headers be included to guarantee that they compile correctly? Are the headers C, C++, Objective-C++, or one of the variants of these languages? What declarations in those headers are actually meant to be part of the API, and what declarations are present only because they had to be written as part of the header file?

A solution to these problems was already introduced in the first drafts of C++0x : the Modules feature. However, it was postponed with each new standard specification.

Modules: a more robust semantic model

Modules improve access to libraries with a more robust and more efficient semantic model. From the user's perspective, the code looks only slightly different, because one uses an import declaration rather than a #include preprocessor directive. Here's an example from the C++0x draft:

import std; // Module import directive.
int main() {
    std::cout << "Hello World\n";
}

There is no need to include multiple STL files—just one import is sufficient, making the code cleaner. The module import loads a binary representation of the std module and makes its API available to the application directly. Preprocessor definitions that precede the import declaration have no impact on the API provided by std, because the module itself was compiled as a separate, standalone module.

Here's an interesting post showing how to develop a C++ module with VS2017: C++ modules in Visual Studio 2017.

Conclusion

C++ modules will eliminate the problems associated with the include mechanism and make using other libraries much easier, as it is in C# or Java.

Try creating your first module and exploring this powerful feature.

Share this article