C++20: Modulunterstützung der großen drei Compiler
Module gehören zu den vier wichtigen Neuerungen in C++20. Zeit für einen Blick darauf, wie gut die Großen Drei (GCC, Clang und MSVC) mit Modulen arbeiten.
- Rainer Grimm
Module sind eine der großen vier Neuerungen in C++20. In meinen C++20-Klassen sind sie eines der Hauptthemen. Leider lag die Implementierung in GCC und Clang weit hinter der des Microsoft Compilers zurück. Deshalb habe ich in meinen Klassen, Vorträgen und Büchern meist den Microsoft Compiler verwendet, um Module vorzustellen. Ich freue mich, dass sich die Modulunterstützung von GCC und Clang verbessert hat. Deshalb werde ich in diesem Artikel den aktuellen Stand der Modulimplementierung (10/2023) der großen drei Compiler GCC, Clang und MSVC vorstellen.
In den letzten vier Jahren habe ich fast 100 Artikel über C++20 geschrieben, aber ich bin noch nicht fertig. In meinem nächsten Artikel werde ich meine Geschichte über C++20 fortsetzen.
Wer mit Modulen in C++20 nicht vertraut ist, findet hier ein einfaches Beispiel:
Ein einfaches Modul
Dies ist das Modul math
.
// math1.ixx
module; // (1)
#include <numeric>
#include <vector>
export module math; // (2)
export int add(int fir, int sec){
return fir + sec;
}
export int getProduct(const std::vector<int>& vec) {
return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
}
Das globale Modulfragment beginnt mit dem Schlüsselwort module
(1) und endet mit der exportierenden Moduldeklaration (3). Das globale Modulfragment ist der Ort, an dem Präprozessoranweisungen wie #include
verwendet werden, damit das Modul kompiliert werden kann. Präprozessor-Entities, die innerhalb des globalen Modulfragments verwendet werden, sind nur innerhalb des Moduls sichtbar. Das Modul math
exportiert die beiden Funktionen add
und getProduct.
Der Client importiert das Modul math
(1) und nutzt seine Funktionen:
// client1.cpp
#include <iostream>
#include <vector>
import math; // (1)
int main() {
std::cout << '\n';
std::cout << "add(2000, 20): " << add(2000, 20) << '\n';
std::vector<int> myVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::cout << "getProduct(myVec): " << getProduct(myVec) << '\n';
std::cout << '\n';
}
Dies ist die Ausgabe des Programms:
Mehr über Module findet sich in folgenden Artikeln:
- Die Vorteile von Modulen
- Ein einfaches math Modul
- Module Interface Unit und Module Implementation Unit
- Module strukturieren
- Weitere offene Fragen zu Modulen
- Private Module Fragment und Header Units
Ich habe mit einem einfachen Modul begonnen und werde es noch einfacher machen, wenn ich den Modulimplementierungsstatus von GCC, Clang und MSVC vorstelle.
Die mit C++20 eingeführten Concepts haben zusammen mit der Ranges-Bibliothek, Modulen und Coroutinen das Erstellen von modernen C++- Anwendungen neu definiert. Vom 7. bis zum 9. November 2023 bringt Sie Rainer Grimm in seinem Intensiv-Workshop C++20: die neuen Konzepte umfassend erklärt auf Stand und geht auf die vielen nützlichen Funktionen ein, die C++20 mitbringt.
Modulunterstützung der Großen Drei
math.ixx
definiert das Modul math
, das ich im folgenden Vergleich verwenden werde.
// math.ixx
export module math; // (1)
export int add(int fir, int sec){
return fir + sec;
}
Außerdem ist hier das Client-Programm client.cpp
, das das Modul math
importiert.
// client.cpp
import math; // (2)
int main() {
add(2000, 20);
}
(1) ist die exportierende Moduldeklaration, und (2) importiert das Modul. Aus offensichtlichen Gründen werde ich die Ausgabe des Programms ignorieren.
Warum ich die Moduldeklarationsdatei math.ixx
genannt habe, könnte zunächst irritieren.
Moduldeklarationsdatei
- Der Microsoft-Compiler verwendet das Suffix
ixx
. Das Suffixixx
steht für eine Modul-Interface Datei. - Der Clang-Compiler verwendet das Suffix
cppm
. Dasm
im Suffix steht wohl für Modul. - Der GCC-Compiler verwendet keine spezielle Erweiterung.
Dies sind nur die Konventionen, die sich ändern lassen. Nun werde ich das Modul math
kompilieren und verwenden.
Kompilieren und Verwenden des Moduls
Zuerst beginne ich mit Microsofts cl.exe 19.29.30133 für x64 Compiler.
- Microsoft-Compiler
Dies sind die Schritte zum Kompilieren und Verwenden des Moduls mit dem Microsoft-Compiler. Ich zeige nur die minimale Befehlszeile. Wie versprochen, werde ich im nächsten Artikel mehr dazu schreiben. Bei einem älteren Microsoft-Compiler muss statt dem Flag /std:c++20
das Flag /std:c++latest
verwendet werden.
cl.exe /std:c++20 /c math.ixx
cl.exe /std:c++20 client.cpp math.obj
- Zeile 1 erstellt eine Objekt-Datei
math.obj
und eine IFC-Dateimath.ifc
. Die IFC-Datei ist das Modul und enthält die Metadatenbeschreibung der Modulschnittstelle. Das Binärformat der IFC-Datei folgt dem Format Internal Program Representation von Gabriel Dos Reis und Bjarne Stroustrup. - Zeile 2 erstellt die ausführbare Datei
client.exe
. Der Compiler findet implizit die kompiliertemath.ifc
Datei aus dem ersten Schritt.
Weiter geht es mit dem Clang-Compiler.
- Clang-Compiler
Ich verwende den Clang 16.0.5 Compiler.
Der Clang-Compiler erwartet ein Modul mit der Erweiterung cppm
. Daher werde ich die Datei math.ixx
in math.cppm
umbenennen. Die Datei client.cpp
lässt sich hingegen unverändert weiterverwenden. Abschließend sind hier die entsprechenden Schritte zum Erstellen und Verwenden des Moduls:
clang++ -std=c++20 -c math.cppm --precompile -o math.pcm
clang++ -std=c++20 client.cpp -fprebuilt-module-path=. math.pcm -o client.exe
- Zeile 1 erstellt das Modul
math.pcm
. Die Endung pcm steht für precompiled module und entspricht derifc
-Dateierweiterung des Microsoft Visual Compilers. Außerdem enthält das erzeugte Modul bereits die Moduldefinition. Folglich erzeugt der Clang-Compiler keine Objektdateimath.o
. Die Option--precompile
ist notwendig, um das vorkompilierte Modul zu erstellen. - Zeile 2 erstellt die ausführbare Datei
client.exe
, die das Modulmath.pcm
verwendet. Der Clang-Compiler benötigt den Pfad zu dem Modul:-fprebuilt-module-path
. Falls dieser nicht bekannt ist, schlägt der Link-Prozess fehl:
Zum Abschluss verwende ich den GCC-Compiler.
- GCC-Compiler
Nun kommt der GCC 11.1.0 Compiler zum Einsatz.
Der GCC-Compiler erwartet weder das Suffix ixx
von Windows noch das Suffix cppm
von Clang. Deshalb benenne ich die Datei math.ixx
in eine cpp-Datei um: math.cxx
. Die Datei client.cpp
ist identisch mit der, die ich mit dem Microsoft und dem Clang-Compiler verwendet habe.
g++ -c -std=c++20 -fmodules-ts math.cxx
g++ -std=c++20 -fmodules-ts client.cpp math.o -o client
- Zeile 1 erstellt das Modul
math.gcm
und die Objektdateimath.o
. Ich muss dafür das Flag-fmodules-ts
angeben. Die Erweiterung-fmodules-ts
irritiert mich, weilts
normalerweise für technische Spezifikation steht. Das Modulmath.gcm
liegt im Unterverzeichnisgcm.cache. math.gcm
ist die kompilierte Modulschnittstelle. Das Suffixgcm
steht wohl für GCC compiled module. - Zeile 2 erstellt den ausführbaren Client. Er verwendet implizit das Modul
math.gcm
.
Wie geht's weiter?
In diesem Artikel habe ich dir die ersten Schritte gezeigt, wie du ein Modul mit den Big Three erstellen kannst. In meinem nächsten Artikel werde ich tiefer in die Modulunterstützung der Großen Drei einsteigen. (rme)