zurück zum Artikel

C++20: Weitere offene Fragen zu Modulen

Rainer Grimm

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: Weitere offene Fragen zu Modulen

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

template <typename T, typename T2>
auto sum(T fir, T2 sec) {
return fir + sec;
}
// 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

export module math;

export namespace math {

template <typename T, typename T2>
auto sum(T fir, T2 sec) {
return fir + sec;
}

}
// 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.

C++20: Weitere offene Fragen zu Modulen

Mit Modulen erhalten wir eine neue Art von Linkage.

Bisher besitzt C++ zwei Arten von Linkage: Internal Linkage und External Linkage.

Mit Modulen erhalten wir Module Linkage.

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

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

#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.

C++20: Weitere offene Fragen zu Modulen

Weder der GCC noch der Clang Compiler unterstützen das nächste Feature. Es wird wohl sehr gerne eingesetzt werden.

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 #include 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].

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.

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