C++ Modules
Published on October 28th 2018

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.

Trying it out

Modules can currently be enabled using compiler flags:

  • Clang and GCC: -fmodules-ts
  • MSVC: /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.