One of the more exciting proposals for the upcoming C++20 revision is the module system (ISO/IEC TS 21544:2018). This proposal aims to solve a number of problems with C++ - not the least of which is compile times for large code bases. In this post, we will have a brief look at how modules can be used with current tooling.
Consider the following Hello World program (courtesy of Wikipedia):
#include <iostream>
int main()
{
std::cout << "Hello, World!" << std::endl;
return 0;
}
An #include
directive essentially tells the preprocessor to copy the contents of the referenced header file into the
current translation unit. When taking transitive inclusion into account, you end up with a massively bloated input to
the compile stage:
$ cpp -E hello_world.cpp | wc -l
28259
Over 28 kLOC for such a simple program! There is no way for us to specify which parts of <iostream>
we need, and so we
end up with everything. This also highlights another problem with #include
- everything in the header file becomes
the public interface. Applications might end up depending on details outside of the intended API. Some libraries, like
Boost, end up using a detail
(or similarly named) namespace for things that applications aren't supposed to rely on,
and make that explicit.
Modules solve this by eliminating #include
altogether. Let's see how we can use them.
Modules can currently be enabled using compiler flags:
-fmodules-ts
/experimental:module
There is as of writing this no support for modules in CMake, so we have to roll our own:
cmake_minimum_required(VERSION 3.2...3.13)
project(cpp_modules VERSION 0.1.0 LANGUAGES CXX)
include(CXXModules.cmake)
add_module_library(math math.cpp)
add_module_executable(cxx_modules main.cpp)
target_link_module_libraries(cxx_modules math)
Next, we define a simple math library that exports a module:
export module math;
export namespace math {
int add(int x, int y) {
return x + y;
}
}
Note how we define a namespace with the same name as our module. This is not in any way required - in fact, modules (like headers) are orthogonal to namespaces. This means that a namespace could be implemented across several modules, which is useful for things like the standard library.
Let's have a look at how modules are consumed:
import math;
int main(int argc, char* argv[]) {
math::add(1, 2);
}
Enjoyed this post? Got feedback? Consider following @Flygsand for the latest updates.
This post, excluding sample code, is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. See the example directory for code licensing.
© Martin Häger, 2018 - 2023.
v1.5.17 (changelog). Built with GraphQL, React and Semantic UI.