zurück zum Artikel

Von Variadic Templates zu Fold Expressions

Rainer Grimm

Nach den BeitrĂ€gen "Variadic Templates oder die Power der drei Punkte" und "Mehr ĂŒber Variadic Templates ... " geht es einen Schritt weiter in die Zukunft mit Fold Expressions, die ein Parameterpack direkt mit einem binĂ€ren Operator reduzieren können.

Nach den BeitrĂ€gen "Variadic Templates oder die Power der drei Punkte [1]" und "Mehr ĂŒber Variadic Templates ... [2]" geht es einen Schritt weiter in die Zukunft mit Fold Expressions, die ein Parameterpack direkt mit einem binĂ€ren Operator reduzieren können.

Von Variadic Templates zu Fold Expressions

Ich habe absichtlich einen wichtigen Anwendungsfall fĂŒr Variadic Templates ausgelassen: Wer Perfect Forwarding mit Variadic Templates kombiniert, erhĂ€lt die perfekte Fabrikfunktion, eine Funktion, die eine beliebige Anzahl von Argumenten beliebiger Wertkategorie (lWert oder rWert) annehmen kann. Neugierig? Mehr findet sich in meinen vorherigen Beitrag "C++ Core Guidelines: Regeln fĂŒr Variadische Templates [3]".

Jetzt möchte ich etwas Neues vorstellen: Fold Expressions.

C++11 unterstĂŒtzt Variadic Templates. Das sind Templates, die eine beliebige Anzahl von Template-Argumenten annehmen können. Die beliebige Anzahl wird in einem sogenannten Parameterpack gebunden. Mit C++17 gibt es außerdem Fold Expressions, mit denen sich ein Parameterpack direkt mit einem binĂ€ren Operator reduzieren lĂ€sst. Folgender Code reduziert beliebig viele Zahlen auf einen Wert:

// variadicTemplatesFoldExpression.cpp

#include <iostream>

bool allVar() { // (1)
return true;
}

template<typename T, typename ...Ts> // (2)
bool allVar(T t, Ts ... ts) { // (3)
return t && allVar(ts...); // (4)
}

template<typename... Args> // (5)
bool all(Args... args) { return (... && args); }

int main() {

std::cout << std::boolalpha;

std::cout << '\n';

std::cout << "allVar(): " << allVar() << '\n';
std::cout << "all(): " << all() << '\n';

std::cout << "allVar(true): " << allVar(true) << '\n';
std::cout << "all(true): " << all(true) << '\n';

std::cout << "allVar(true, true, true, false): "
<< allVar(true, true, true, false) << '\n';
std::cout << "all(true, true, true, false): "
<< all(true, true, true, false) << '\n';

std::cout << '\n';

}

Die beiden Templates allVar und all geben zur Kompilierzeit zurĂŒck, wenn alle Argumente wahr sind. allVar verwendet ein Variadic Template; all Fold Expressions.

Das Variadic Template allVar wendet Rekursion an, um seine Argumente auszuwerten. Die Funktion allVar (1) ist die Randbedingung, falls das Parameterpack leer ist. In dem Funktions-Template allVar (2) findet die Rekursion statt. Die drei Punkte stehen fĂŒr das sogenannte Parameterpack. Diese unterstĂŒtzen zwei Operationen. Man kann sie packen oder entpacken. Gepackt wird es in (2), entpackt in (3) und (4).

(4) erfordert unsere volle Aufmerksamkeit: Hier wird der Kopf des Parameterpakets t mit dem Rest ts des Parameterpacks ts mithilfe des binĂ€ren Operators && kombiniert. Der Aufruf allVar(ts ...) löst die Rekursion aus. Der Aufruf enthĂ€lt ein Parameterpack, und zwar das ursprĂŒngliche, das um den Kopf reduziert ist. Fold Expression (5) machen die Arbeit leichter. Mit Fold Expressions lĂ€sst das Parameterpack mithilfe des binĂ€ren Operators direkt reduzieren: (... && args).

Hier ist die Ausgabe des Programms:

Von Variadic Templates zu Fold Expressions


Eine Fold Expression wendet einen binÀren Operator auf ein Parameterpack an.

Eine Fold Expression kann den binÀren Operator auf zwei verschiedene Arten anwenden.

Es gibt einen feinen Unterschied zwischen dem Algorithmus allVar und all. Letzterer besitzt den Standardwert true fĂŒr das leere Parameterpack.

C++17 unterstĂŒtzt 32 binĂ€re Operatoren in Fold Expressions: + - * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->* . Einige von ihnen haben Default-Werte:

Von Variadic Templates zu Fold Expressions

BinĂ€re Operatoren, die keinen Standardwert haben, erfordern einen Anfangswert. FĂŒr binĂ€re Operatoren, die einen Standardwert haben, ist der Anfangswert optional.

Wenn die Ellipse links vom Parameterpack steht, wird das Parameterpaket von links verarbeitet. Steht sie rechts davon, wird es von rechts verarbeitet. Das gilt auch, wenn ein Anfangswert zum Einsatz kommt.

Die Fold Expression ermöglicht es, Haskells Funktionen foldl, foldr, foldl1 und foldr1 direkt in C++ zu implementieren.

Die folgende Tabelle zeigt die vier Varianten von Fold Expressions und ihre Haskell-Pendants. Der C++17-Standard schreibt vor, dass Fold Expresson mit Anfangswert den gleichen binÀren Operator op verwenden.

Von Variadic Templates zu Fold Expressions


Die C++- und Haskell-Varianten unterscheiden sich in zwei Punkten. Die C++-Version verwendet den Standardwert und die Haskell-Version das erste Element als Anfangswert. Die C++-Variante verarbeitet das Parameterpack zur Kompilierzeit, wÀhrend die Haskell-Variante seine Liste zur Laufzeit verarbeitet.

Das folgende Programm zeigt alle vier Varianten der Fold Expression. Jede subtrahiert ein paar Zahlen.

// foldVariations.cpp

#include <iostream>

template<typename... Args> // (1)
auto diffL1(Args const&... args) {
return (... - args);
}

template<typename... Args> // (2)
auto diffR1(Args const&... args) {
return (args - ...);
}

template<typename Init, typename... Args> // (3)
auto diffL(Init init, Args const&... args) {
return (init - ... - args);
}

template<typename Init, typename... Args> // (4)
auto diffR(Init init, Args const&... args) {
return (args - ... - init);
}

int main() {

std::cout << '\n';

// (1 - 2) - 3:
std::cout << "diffL1(1, 2, 3): " << diffL1(1, 2, 3) << '\n';
// 1 - (2 - 3)
std::cout << "diffR1(1, 2, 3): " << diffR1(1, 2, 3) << '\n';
// ((10 - 1) - 2) - 3:
std::cout << "diffL(10, 1, 2, 3): " << diffL(10, 1, 2, 3) << '\n';
// 1 - (2 - (3 - 10)):
std::cout << "diffR(10, 1, 2, 3): " << diffR(10, 1, 2, 3) << '\n';

std::cout << '\n';

}

Die Funktionen diffL1 (1) und diffL (3) verarbeiten die Zahlen von links und die Funktionen diffR1 (2) und diffR (3) verarbeiten sie von rechts. Außerdem verwenden die Funktionen diffL und diffR einen Startwert. In den Kommentaren in der main-Funktion habe ich die Verarbeitungsschritte dargestellt.

Von Variadic Templates zu Fold Expressions


Ich fĂŒhre das Haskell-Pendant direkt in der Haskell-Shell aus.

Von Variadic Templates zu Fold Expressions


Variadic Templates und vor allem Fold Expressions ermöglichen es, prĂ€gnante AusdrĂŒcke fĂŒr wiederholte Operationen zu schreiben. Mehr dazu in meinem nĂ€chsten Beitrag. ( [4])


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

Links in diesem Artikel:
[1] https://heise.de/-6157802
[2] https://heise.de/-6165404
[3] https://heise.de/-4259632
[4] mailto:rainer@grimm-jaud.de