Skip to content

Latest commit

 

History

History
318 lines (228 loc) · 10.3 KB

File metadata and controls

318 lines (228 loc) · 10.3 KB

Teil I: Variadische Templates - Einführung

Zurück


Teil II: Variadische Templates: Zugriff auf jedes einzelne Argument


Quellcode


Inhalt


Einleitung

Ab C++–11 gibt es in C++ die so genannten variadischen Templates (Variadic Templates). Darunter versteht man Klassen- oder Funktionstemplates , die eine beliebige Anzahl von Argumenten annehmen können.

In klassischem C++ können Templates nur eine feste Anzahl von Parametern haben, die bei der Deklaration angegeben werden müssen. Variadische Templates helfen jedoch, dieses Problem zu überwinden.

Das Konzept für diese Spracherweiterung wurde von Douglas Gregor und Jaakko Järvi entwickelt.

Grundsätzlich werden zwei neue syntaktische Elemente für die Definition derartiger Templates benötigt: Zum einen das so genannte Parameter Pack zur Deklaration des Templates, zum anderen die Expansion des Parameter Packs in der Definition des Templates.

Das Parameter Pack

Ein Parameter Pack ist einfach ein Name, der einer Liste von Templateparametern anstelle eines einzelnen Parameters zugewiesen wird. Die häufigsten zwei Arten von Templateparametern und damit Möglichkeiten, Parameter Packs anzuwenden lauten:

template <typename... TS>    // TS is a list of type parameters
template <unsigned... NS>    // NS is a list of non-type parameters (unsigned)

Der erste Fall tritt in der Praxis am häufigsten auf. Wie bei „normalen” Vorlagen können variadische Templates Funktionstemplates oder Klassentemplates sein. Wir können auch einzelne Parameter und Parameter Packs mischen, mit der Einschränkung, dass es nur eine einzige Parameter Pack Definition geben kann und diese am Ende der Parameterliste stehen muss:

template <typename T, int N, typename... TS>

Zusätzlich zu Template Parameter Packs haben wir Function Parameter Packs. Sie kommen zum Einsatz, wenn in einem variadischen Funktionstemplate das Template Parameter Pack zum Definieren von Funktionsargumenten verwendet wird. Dies sollten wir an einem Beispiel veranschaulichen:

template <typename... TArgs>    // TArgs is the template parameter pack
void func (TArgs... args) {     // args is the function parameter pack
  //...
}

Noch etwas formale Formulierungen gewünscht? Bitte schön: Wenn in Betrachtung der Funktionsschablone func TArgsT1, T2 ist, dann gilt

  • TArgs ... argsT1 t1, T2 t2 und
  • args ... ≙ t1, t2

Expansion eines Parameter Packs: Datentypen und Argumente

Es ist nicht möglich, ein Parameter Pack anders zu verwenden als es zu erweitern. In den meisten Fällen ergibt die Erweiterung des Parameter Packs eine durch Kommas getrennte Liste von Ausdrücken, die die einzelnen Elemente des Packs enthalten.

Die einfachste Pack-Expansion ist nur der Name des Parameter Packs, gefolgt von der Ellipse (...), was zu einer durch komma-getrennten Liste der Pack-Elemente führt:

template <typename... TArgs>
void examine_parameter_pack(TArgs ... args) {
    std::tuple<TArgs ...> tup { args ... };
}

oder ab C++ 20:

void examine_parameter_pack_ex(auto ... args) {
    std::tuple tup1{ args ... };
}

Betrachten wir ein Beispiel:

examine_parameter_pack(123, 123.456, std::string{ "foo" }, 789ll);

In dem Funktionsaufruf von examine_parameter_pack definieren die vier Parameter zwei Parameter Packs. Das Template Parameter Pack TArgs ist die Liste der Typen int, double, std::string und long long, während das Function Parameter Pack args die Liste mit den Werten 123, 123.456, std::string{ "foo" } und 789ll darstellt.

Diese betrachtete Instanziierung der Funktionsvorlage entspricht quasi dem, als hätten wir geschrieben:

void examine_parameter_pack_ex_ex(int args_1, double args_2, std::string args_3, long long args_4) {
    std::tuple<int, double, std::string, long long> tup{ args_1, args_2, args_3, args_4 };
}

Nur um es kurz angesprochen zu haben: Bei std::tuple handelt es sich ebenfalls um ein variadisches Klassentemplate.

Ein einfaches Beispiel: add-Funktion mit beliebig vielen Argumenten

Eine Funktionsschablone zur Addition beliebig vieler Zahlen (exakter: zur Anwendung des +-Operators auf beliebig viele Objekte) kann als variadisches Template so formuliert werden:

// Non-recursive template part (regular template)
template<typename T>
T add(T v) {
    return v;
}

// Recursive template part
// Note: ... specifies a so called 'parameter pack')
template<typename T, typename ...Args>
T add(T first, Args... args) {
    return first + add(args...);
}

Mögliche Anwendungsfälle dieser Funktionsschablone:

int sum = add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
std::cout << "Sum from 1 up to 10: " << sum << std::endl;

std::string stringConcat = add(
    std::string("ABC"),
    std::string("DEF"),
    std::string("GHI"),
    std::string("JKL"),
    std::string("MNO")
);
std::cout << "String Concatenation: " << stringConcat << std::endl;

Zugriff auf die einzelnen Elemente eines Parameter Packs

Prinzipiell ist der Zugriff auf die einzelnen Elemente eines Parameter Packs nicht vorgesehen.

Sind alle Elemente vom selben Typ, kann man diese mit Hilfe eines std::initializer_list-Objekts entpacken:

template <typename... TArgs>
void func(TArgs... args) {

    // unpack all function arguments with the help of a std::initializer_list object
    auto unpackedArgs = { args ... };

    for (auto param : unpackedArgs) {
        std::cout << "Passed Argument: " << param << std::endl;
    }

    // oder

    for (auto param : { args ... } ) {
        std::cout << "Passed Argument: " << param << std::endl;
    }
}

static void test_accessing_parameterpack()
{
    func(10, 11, 12, 13, 14, 15);
}

Leeres Parameter Pack

Ein Parameter Pack kann prinzipiell auch keine Parameter annehmen. In diesen Fällen ergibt die Pack-Expansion eine leere Liste. In Fällen, in denen der Pack-Expansion ein Komma vorangestellt ist, wird dieses Komma vom Übersetzer ignoriert. In unserem obigen Beispiel würde der Aufruf f(22) eine Template-Instanziierung wie folgt ergeben:

template<>
void f(int i /*, ignored comma before empty pack expansion */) {
  std::tuple<> argsTuple{}; 
}

Wo können Parameter Pack Expansionen auftreten?

Unsere bisherigen Beispiele haben sich auf den Gebrauch von Parameter Pack Expansionen konzentriert, um eine Folge von Template Argumenten zu erzeugen. In der Tat können Parameter Pack Expansionen prinzipiell überall in C ++ zum Einsatz kommen, wo die Grammatik eine durch Kommas getrennte Liste gestattet:

Einsatzmöglichkeiten von Parameter Pack Expansionen
In der Liste der Basisklassen
In der Liste der Basisklassen Initialisierer in einem Konstruktor
In der Liste von Aufrufargumenten eines Methodenaufrufs
In der Liste von Initialisierern (z. B. in einer geschweiften Initialisiererliste - std::initializer_list)
In der Template Parameterliste eines Klassen oder Funktionstemplate
In der Parameterliste eines Funktionstyps
Bei der Verwendung von using-Deklarationen

Tabelle 1. Einsatzmöglichkeiten von Parameter Pack Expansionen.

Der sizeof... Operator

Der sizeof...-Operator ist eine spezielle Form der Parameter Pack Expansion. Es gibt einfach die Anzahl der Parameter Pack Elemente zurück und funktioniert sowohl mit Template Parameter Packs als auch mit Function Parameter Packs:

template <typename... Ts>
void printCount(Ts... args) {
  std::cout 
    << sizeof...(Ts) << ' '
    << sizeof...(args) << '\n';
}

Test des Function Templates am Beispiel

printCount(22, std::optional{0}, "!");

Ausgabe:

3 3

Ein erstes Anwendungsbeispiel: Abbildung eines Parameter Packs auf einen Methodenaufruf

Es wird demonstriert, wie ein Parameter Pack auf einen Methodenaufruf (hier: Konstruktor) abgebildet werden kann ("Unpacking a Parameter Pack to call a matching constructor").

Zu diesem Zweck definieren wir eine Klasse Unknown mit einer Reihe von Konstruktoren, um zu zeigen, wie das Auspacken des Parameter Packs dem passenden Konstruktor zugeordnet wird:

Es kommt das „perfekte Forwarding” zum Zuge:

T(std::forward<Args>(args)...);

Siehe hierzu den Quellcode.

Ein zweites Anwendungsbeispiel: Verwendung eines Parameter Packs bei Smart Pointern (hier: Unique Pointern)

Siehe hierzu den Quellcode.


Literaturhinweise

Einführende Artikel zum Thema der varadischen Templates finden sich zum Beispiel unter

Arne Mertz Introduction
(abgerufen am 11.05.2020).

Arne Mertz More About Variadic Templates
(abgerufen am 11.05.2020).

Kevin Ushey Introduction to C++ Variadic Templates
(abgerufen am 11.05.2020).

Craig Scott Practical uses for Variadic Templates
(abgerufen am 11.05.2020).

und

Florian Weber Using Variadic Templates cleanly
(abgerufen am 11.05.2020).


Zurück