C++ Modules (WIP)
Introduction
In C++ 20 and beyond, modules can be used instead of include files to help structure software projects and improve compile time. This article provides some (very) basic notes on their usage.
A Basic Example
Consider the following files
// math.cppm
export module math;
export int add(int a, int b);
export void print_int(int a);// math.cpp
module math;
import <iostream>;
import <sstream>;
int add(int a, int b) {
return a + b;
}
void print_int(int a) {
std::cout << a;
}// main.cpp
import math;
#include <iostream>
int main() {
std::cout << add(2,3) << "\n";
}These define and run a basic module called math. This
has two methods, the first takes two integers and returns their sum. The
second prints those integers. The module methods are defined in the
math.cppm file - this is similar to header files in
traditional C++. The implementation of the module is done in the
math.cpp file, and the methods are used in
main.cpp.
To compile the module, the following GCC command is used:
g++ -std=c++20 -fmodules-ts math.cppm math.cpp main.cpp
However, this will likely fail with the following error
In module imported at math.cpp:3:1:
/usr/include/c++/16/iostream: error: failed to read compiled module: No such file or directory
/usr/include/c++/16/iostream: note: compiled module file is ‘gcm.cache/./usr/include/c++/16/iostream.gcm’
/usr/include/c++/16/iostream: note: imports must be built before being imported
/usr/include/c++/16/iostream: fatal error: returning to the gate for a mechanical issue
compilation terminated.
This is due to the standard library, which has been imported in
math.cpp, not having compiled modules. Modules, even those
provided by the C++ standard library, must be compiled before they can
be used in compilation. Compilers do not ship modules pre-compiled as
their compilation depends on the compiler arguments used,
e.g. optimisation and debug symbols.
Using Modules for the Standard Library
G++ provides a convenient way to compile an individual header file for exactly the purpose described above:
g++ -std=c++20 -fmodules-ts -x c++-system-header <HEADER_NAME>
However, doing this for every standard library module is tedious. The following Makefile can be used to compile all the standard library modules:
# system_headers.make
CXX := g++
CXXFLAGS := -std=c++20 -fmodules-ts
# Add or remove headers as desired.
STD_HEADERS := \
algorithm \
any \
array \
atomic \
bit \
bitset \
chrono \
compare \
complex \
concepts \
coroutine \
deque \
exception \
filesystem \
format \
forward_list \
functional \
future \
initializer_list \
iomanip \
ios \
iosfwd \
iostream \
iterator \
limits \
list \
map \
memory \
mutex \
numbers \
numeric \
optional \
queue \
random \
ranges \
ratio \
regex \
set \
span \
sstream \
stack \
stdexcept \
stop_token \
streambuf \
string \
string_view \
system_error \
thread \
tuple \
type_traits \
typeindex \
typeinfo \
unordered_map \
unordered_set \
utility \
valarray \
variant \
vector
.PHONY: system_headers system_headers_clean
system_headers: $(STD_HEADERS:%=.stamp-%)
.stamp-%:
$(CXX) $(CXXFLAGS) -x c++-system-header $*
@touch $@
system_headers_clean:
rm -f .stamp-*
rm -rf gcm.cache
This can then be used to compile the previous project using this Makefile:
# Makefile
include system_headers.make
.PHONY: all clean
.DEFAULT_GOAL := all
all: main
main: system_headers
g++ -std=c++20 -fmodules-ts math.cppm math.cpp main.cpp -o main
clean: system_headers_clean
rm -f main