C++20: Weitere offene Fragen zu Modulen
Die bisherigen Artikel zu Modulen gingen auf die Grundlagen ein. Übrig bleiben Themen wie das Zusammenspiel mit Templates, das Linkage sowie Header Units.
Die bisherigen vier Artikel zu Modulen gingen auf deren Grundlagen ein. Daher gibt es nur noch wenige Fragen zu Modulen zu beantworten. Genau diese Fragen adressiere ich in dem heutigen Artikel: Templates und Module, das Linkage von Modulen und Header Units.
In diesem Artikel mache ich es mir einfach. Ich nehme an, dass du meine vorherigen Artikel zu Modulen kennst. Falls nicht, habe ich sie hier aufgelistet:
- C++20: Die Vorteile von Modulen [1]
- C++20: Ein einfaches math-Modul [2]
- C++20: Module Interface Unit und Module Implementation Unit [3]
- C++20: Module strukturieren [4]
Templates und Module
Ich höre recht häufig die Frage: Wie werden Templates durch Module exportiert? Wenn du ein Template instanziierst, muss seine Definition verfügbar sein. Das ist der Grund, warum Template-Definitionen in Header-Dateien verpackt werden. Konzeptionell besitzt die Verwendung von Templates die folgende Struktur.
templateSum.h
// templateSum.h
template <typename T, typename T2>
auto sum(T fir, T2 sec) {
return fir + sec;
}
sumMain.cpp
// sumMain.cpp
#include <templateSum.h>
int main() {
sum(1, 1.5);
}
Die Datei sumMain.cpp
inkludiert direkt die Header-Datei templatSum
.h
. Der Aufruf sum(1, 1.5)
stößt die Template-Instanziierung an. In diesem Fall erzeugt der Compiler aus dem Funktions-Template sum
die konkrete Funktion sum
, die ein int
und ein double
erwartet. Dieser Prozess lässt sich schön mit C++ Insights [5] visualisieren.
Templates können und sollten mit C++20 in Modulen definiert werden. Module habe eine eindeutige interne Repräsentation, die weder Sourcecode noch Assembler entspricht. Diese Repräsentation ist eine Art Abstract Syntax Tree [6] (AST). Dank ihr steht die Template-Definition während der Template-Instanziierung zur Verfügung.
Im folgenden Beispiel definiere ich das Funktions-Template sum
in dem Modul math
.
mathModuleTemplate.ixx
// mathModuleTemplate.ixx
export module math;
export namespace math {
template <typename T, typename T2>
auto sum(T fir, T2 sec) {
return fir + sec;
}
}
clientTemplate.cpp
// clientTemplate.cpp
#include <iostream>
import math;
int main() {
std::cout << std::endl;
std::cout << "math::sum(2000, 11): " << math::sum(2000, 11) << std::endl;
std::cout << "math::sum(2013.5, 0.5): " << math::sum(2013.5, 0.5) << std::endl;
std::cout << "math::sum(2017, false): " << math::sum(2017, false) << std::endl;
}
Die Kommendozeile zum Übersetzen des Programms unterscheidet sich nicht von der des letzten Artikels "C++20: Module strukturieren [7]". Daher verzichte ich auf diese und zeige direkt die Ausgabe des Programms.
Mit Modulen erhalten wir eine neue Art von Linkage.
Modul Linkage
Bisher besitzt C++ zwei Arten von Linkage: Internal Linkage und External Linkage.
- Internal Linkage: Namen mit Interal Linkage lassen sich nicht außerhalb der Übersetzungseinheit verwenden. Es schließt im Wesentlichen globale Namen (namespace scope) ein, die als
static
deklariert sind, und Mitglieder anonymer Namensräume. - External Linkage: Namen mit External Linkage lassen sich außerhalb der Übersetzungseinheit verwenden. Es schließt im Wesentlichen globale Namen ein, die nicht
static
deklariert, aber auch Klassen und ihre Mitglieder, Variablen und Templates.
Mit Modulen erhalten wir Module Linkage.
- Module Linkage: Namen mit Module Linkage lassen sich nur innerhalb des Moduls verwenden. Damit Namen Module Linkage besitzen, sind zwei Bedingungen notwendig. Einerseits dürfen die Namen keine Internal Linkage haben und damit nur in der Übersetzungseinheit sichtbar sein und andererseits dürfen die Namen nicht exportiert werden.
Die kleine Variation des vorherigen Moduls math
soll Module Linkage verdeutlichen. Ich möchte Anwendern meines Funktions-Templates sum
zusätzlich zurückgeben, welchen Rückgabetyp der Compiler deduziert hat.
mathModuleTemplate1.ixx
// mathModuleTemplate1.ixx
module;
#include <iostream>
#include <typeinfo>
#include <utility>
export module math;
template <typename T> // (2)
auto showType(T&& t) {
return typeid(std::forward<T>(t)).name();
}
export namespace math { // (3)
template <typename T, typename T2>
auto sum(T fir, T2 sec) {
auto res = fir + sec;
return std::make_pair(res, showType(res)); // (1)
}
}
Anstelle der Summe der Zahlen gibt das Funktions-Template sum
ein std::pair
(1) zurück, das aus der Summe und der String-Repräsentation des Datentyps res
besteht. Ich habe das Funktions-Template showType
(2) nicht im exportierten Namensraum math
(3) verwendet. Konsequenterweise kann auf showType
nicht außerhalb des Moduls math
zugegriffen werden. showType
nutzt Perfect Forwarding [8], um die Wertkategorie des Funktionsarguments t
zu erhalten. Die Funktion typeid
ermittelt die Information zum Datentyp zur Laufzeit (runtime type identification (RTTI) [9]).
clientTemplate1.cpp
// clientTemplate1.cpp
#include <iostream>
import math;
int main() {
std::cout << std::endl;
auto [val, message] = math::sum(2000, 11);
std::cout << "math::sum(2000, 11): " << val << "; type: " << message << std::endl;
auto [val1, message1] = math::sum(2013.5, 0.5);
std::cout << "math::sum(2013.5, 0.5): " << val1 << "; type: " << message1 << std::endl;
auto [val2, message2] = math::sum(2017, false);
std::cout << "math::sum(2017, false): " << val2 << "; type: " << message2 << std::endl;
}
Jetzt stellt das Programm das Ergebnis der Summation und die String-Repräsentation des automatisch ermittelten Rückgabetyps dar.
Weder der GCC noch der Clang Compiler unterstützen das nächste Feature. Es wird wohl sehr gerne eingesetzt werden.
Header Units
Header Units stellen eine sehr angenehme Art dar, den Übergang von Header-Dateien und Modulen zu vollziehen. Du musst dazu lediglich die #include
-Direktive durch die neue import
-Direktive ersetzen.
#include <vector> => import <vector>;
#include "myHeader.h" => import "myHeader.h";
Zuerst einmal respektiert import
dieselben Lookup-Regeln wie include
. Das bedeutet in dem konkreten Fall der Anführungszeichen ("myHeaders.h
"), dass der Lookup zuerst das lokale Verzeichnis und dann den Systempfad berücksichtigt.
Darüber hinaus stellt die Ersetzung von #includ
e mit import
mehr als eine Textersetzung dar. Im Fall von import
erzeugt der Compiler eine Einheit, die modulähnlich ist, und behandelt diese wie ein Modul. Der import
-Aufruf importiert alle Namen der Header-Datei, die exportierbar sind. Dies umfasst alle Makros. Das Importieren der erzeugten Header Unit ist schneller und aus Performanzsicht vergleichbar zum Importieren vorkompilierter Header-Dateien [10].
Ein Wermutstropfen
Header Units besitzen einen Wermutstropfen: Nicht alle Header-Dateien sind importierbar. Welche Header-Dateien das sind, hängt vom Compiler ab. Der C++20-Standard garantiert aber, dass alle Header-Dateien des Standards importierbar sind. Das schließt die C-Header-Dateien aus, die nur durch den Namensraum std
dekoriert werden. So ist zum Beispiel die Header-Datei <
cstring
>
der C++-Wrapper für <
string.h
>
. Du kannst die C-Header-Dateien einfach identifizieren, denn aus C-Header-Datei xxx.h
wird cxxx.
Wie geht's weiter
Mit diesem Artikel schließe ich meine Geschichte zu Modulen und insbesondere zu den großen Vier in C++20 ab. Der Link C++20 [11] verweist auf alle existierenden und noch erscheinenden Artikel zu C++20. Als Nächstes nehme ich die Features der Kernsprache genauer unter die Lupe, die nicht so prominent sind wie Concepts oder Module. Der nächste Artikel beschäftigt sich mit dem Drei-Weg-Vergleichsoperator. ( [12])
URL dieses Artikels:
https://www.heise.de/-4776210
Links in diesem Artikel:
[1] https://heise.de/-4717856
[2] https://heise.de/-4722629
[3] https://heise.de/-4727382
[4] https://heise.de/-4770234
[5] https://cppinsights.io/s/cee41fd8
[6] https://en.wikipedia.org/wiki/Abstract_syntax_tree
[7] https://heise.de/-4770234
[8] https://www.grimm-jaud.de/index.php/blog/perfect-forwarding
[9] https://en.cppreference.com/w/cpp/types
[10] https://en.wikipedia.org/wiki/Precompiled_header
[11] https://www.grimm-jaud.de/index.php/blog/category/c-20
[12] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien