zurück zum Artikel

Template Metaprogrammierung: Hybride Programmierung

Rainer Grimm

Hybride Programmierung ist kein offizieller Begriff, betont aber einen interessanten Aspekt von Templates : Den Unterschied zwischen Funktionsargumenten und Templateargumenten.

Hybride Programmierung ist kein offizieller Begriff. Ich habe ihn erfunden, um einen interessanten Aspekt von Templates zu betonen: Den Unterschied zwischen Funktionsargumenten und Templateargumenten.

Template Metaprogrammierung: Hybride Programmierung

Meinen letzten Artikel "Template-Metaprogrammierung: Wie es funktioniert [1]" beendete ich mit einem Rätsel. Zur Erinnerung, hier ist das Rätsel:

Die Funktionen power und Power berechnen pow(2, 10). power wird zur Laufzeit ausgeführt und Power zur Compilezeit.

// power.cpp

#include <iostream>

int power(int m, int n) {
int r = 1;
for(int k = 1; k <= n; ++k) r *= m;
return r;
}

template<int m, int n>
struct Power {
static int const value = m * Power<m, n-1>::value;
};

template<int m>
struct Power<m, 0> {
static int const value = 1;
};

int main() {

std::cout << '\n';

std::cout << "power(2, 10)= " << power(2, 10) << '\n';
std::cout << "Power<2,10>::value= " << Power<2, 10>::value << '\n';

std::cout << '\n';
}

Mehr Details zu beiden Funktionen finden sich in meinem vorherigen Artikel "Template-Metaprogrammierung - Wie es funktioniert [2].

So weit, so gut, aber was passiert in folgendem Beispiel?

// powerHybrid.cpp

#include <iostream>

template<int n>
int Power(int m){
return m * Power<n-1>(m);
}

template<>
int Power<0>(int m){
return 1;
}

int main() {

std::cout << '\n';

std::cout << "Power<0>(10): " << Power<0>(20) << '\n';
std::cout << "Power<1>(10): " << Power<1>(10) << '\n';
std::cout << "Power<2>(10): " << Power<2>(10) << '\n';


std::cout << '\n';

}

Wie erwartet, erledigt Power seine Aufgabe zuverlässig.

Template Metaprogrammierung: Hybride Programmierung

Hier ist das Rätsel in Kurzform: Ist Power eine Funktion oder eine Metafunktion?

Die Aufrufe Power<0>(10), Power<1>(10) und Power<2>(10) verwenden spitze und runde Klammern und potenzieren 10 mit 0, 1 und 2. Das heißt, 0, 1 und 2 sind Compilezeit-Argumente und 10 ist ein Laufzeit-Argument. Anders ausgedrückt heißt dies: Potenz ist gleichzeitig eine Funktion und eine Metafunktion. Ich möchte auf diesen Punkt gerne genauer eingehen.

Zunächst kann ich Power für 2 instanziieren, ihr den Namen Power2of geben und sie in einer for-Schleife verwenden.

// powerHybridRuntime.cpp

#include <iostream>

template<int n>
int Power(int m){
return m * Power<n-1>(m);
}

template<>
int Power<0>(int m){
return 1;
}

int main() {

std::cout << '\n';

auto Power2of = Power<2>;

for (int i = 0; i <= 20; ++i) {
std::cout << "Power2of(" << i << ")= "
<< Power2of(i) << '\n';
}

std::cout << '\n';

}

Power2of ermöglicht es, die Quadrate von 0 ... 20 zur Laufzeit zu berechnen.

Template Metaprogrammierung: Hybride Programmierung


Natürlich kann man Power nicht mit verschiedenen Template-Argumenten in der for-Schleife aufrufen. Die Instanziierung eines Templates erfordert einen konstanten Ausdruck. Um es kurz zu machen: Die folgende Anwendung von Power führt zu einem Compilierfehler, der besagt, dass "the value of 'i' is not usable in a constant expression".

for (int i = 0; i <= 20; ++i) {

std::cout << "Power<" << i << ">(2)= " << Power<i>(2) << '\n';

}

Es gibt einen sehr interessanten Unterschied zwischen einer Funktion und einer Metafunktion.

Wer das vorherige Programm powerHybrid.cpp in C++ Insights untersucht, sieht, dass jeder Einsatz von Power mit einem anderen Template-Argument einen neuen Typ erzeugt.

Das bedeutet, dass der Aufruf von Power<2>(10) die rekursive Template-Instanziierung für Power<1>(10) und Power<0>(10) bewirkt. Hier ist die Ausgabe von C++ Insights.

Template Metaprogrammierung: Hybride Programmierung

Um meine Beobachtung zusammenzufassen: Jede Template-Instanziierung erzeugt einen neuen Typ.

Wer ein Template wie Power, std::vector oder std::array verwendet, kann es mit zwei Arten von Argumenten aufrufen: Funktionsargumente und Template-Argumente. Die Funktionsargumente stehen in runden Klammern (( ... )) und die Template-Argumente stehen in spitzen Klammern (<...>). Mit den Template-Argumenten werden neue Typen erstellt. Oder andersherum formuliert. Man kann Templates auf zwei Arten parametrisieren: zur Compilezeit mit spitzen Klammern (<...>) und zur Laufzeit mit runden Klammern (( ... )).

auto res1 = Power<2>(10);                       // (1)
auto res2 = Power<2>(11); // (2)
auto rest3 = Power<3>(10); // (3)

std::vector<int> myVec1(10); // (1)
std::vector<int> myVec2(10, 5); // (2)
std::vector<double> myDouble(5); // (3)

std::array<int, 3> myArray1{ 1, 2, 3}; // (1)
std::array<int, 3> myArray2{ 1, 2, 3}; // (2)
std::array<double, 3> myArray3{ 1.1, 2.2, 3.3}; // (3)

Ein paar meiner deutschen Leser haben mich bereits darauf hingewiesen: Meine Metafunktion Power hat eine große Schwachstelle.

Wenn ich Power mit einer negativen oder einer zu großen Zahl instanziiere, kommt es zu undefiniertem Verhalten.

Das erste Problem kann durch die Verwendung eines static_assert innerhalb der Power-Templates behoben werden: static_assert(n >= 0, "exponent must be >= 0");. Für das zweite Problem gibt es keine einfache Lösung.

// powerHybridRuntimeOverflow.cpp

#include <iostream>

template<int n>
int Power(int m){
return m * Power<n-1>(m);
}

template<>
int Power<0>(int m){
return 1;
}

int main() {

std::cout << '\n';

auto Power10of = Power<10>;

for (int i = 0; i <= 20; ++i) {
std::cout << "Power10of(" << i << ")= "
<< Power10of(i) << '\n';
}

std::cout << '\n';

}

Der Überlauf beginnt mit Power10of(9). pow(9, 10) ist 3.486.784.40

Template Metaprogrammierung: Hybride Programmierung

Am Ende dieser drei Artikel "Template Metaprogrammierung - Wie alles begann [3]", "Template-Metaprogrammierung: Wie es funktioniert [4]" über Template Metaprogramming möchte ich einen Haftungsausschluss aussprechen. Ich möchte nicht, dass jemand zur Compilezeit mit Templates programmiert. Die meiste Zeit ist constexpr (C++11) oder consteval (C++20) die deutlich bessere Wahl.

Ich habe die Template-Metaprogrammierung aber aus zwei Gründen erklärt.

In meinem nächsten Artikel schreibe ich über die Type-Traits-Bibliothek [6], die Template-Metaprogrammierung in schönen Gewande verkörpert. ( [7])


URL dieses Artikels:
https://www.heise.de/-6266012

Links in diesem Artikel:
[1] https://heise.de/-6237233
[2] https://heise.de/-6237233
[3] https://heise.de/-6233576
[4] https://heise.de/-6237233
[5] https://en.cppreference.com/w/cpp/header/type_traits
[6] https://en.cppreference.com/w/cpp/header/type_traits
[7] mailto:rainer@grimm-jaud.de