C++ Insights: Variadic Templates

Modernes C++  –  0 Kommentare

Variadic Templates sind ein C++-Feature, das beim ersten Blick magisch wirkt. Dank C++ Insights verschwindet die Magie aber schnell.

Variadic Templates sind eines der mächtigsten neuen Konstrukte, die wir seit C++11 haben.

Variadic Templates

Sie sind großartig, weil wir eine Funktion haben können, die mehrere Argumente gleichzeitig akzeptiert und trotzdem stark typisiert ist. Wir benötigen keinen Formatbezeichner, um etwas Speicher vom Stapel in einen Typ umzuwandeln. Variadische Templates oder in diesem Fall genauer variadische Funktions-Templates erweitern sich in Funktionen, wie wir sie schreiben würden. Das sogenannte Parameterpaket wird erweitert. Während dieses Vorgangs wird jeder Parameter einfach durch ein Komma getrennt, so wie wir die Funktion schreiben würden. Hier ist ein grundlegendes Beispiel:

template<typename T>
T add(const T& arg)
{
return arg;
}

template<typename T, typename... ARGS>
T add(const T& arg, const ARGS&... args)
{
return arg + add(args...);
}

int main()
{
return add(1, 2u, 3u);
}

Die Überladung des einzelnen Arguments ist erforderlich, um die hier verwendete Rekursion zu beenden. Verwenden wir C++ Insights, um zu sehen, was unter der Haube passiert:

template<typename T>
T add(const T& arg)
{
return arg;
}

/* First instantiated from: insights.cpp:10 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
unsigned int add<unsigned int>(const unsigned int & arg)
{
return arg;
}
#endif


template<typename T, typename... ARGS>
T add(const T& arg, const ARGS&... args)
{
return arg + add(args...);
}

/* First instantiated from: insights.cpp:15 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int add<int, unsigned int, unsigned int>(const int & arg, const unsigned int & __args1, const unsigned int & __args2)
{
return static_cast<int>(static_cast<unsigned int>(arg) + add(__args1, __args2));
}
#endif


/* First instantiated from: insights.cpp:10 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
unsigned int add<unsigned int, unsigned int>(const unsigned int & arg, const unsigned int & __args1)
{
return arg + add(__args1);
}
#endif


int main()
{
return add(1, 2u, 3u);
}

Achten Sie auf die Typen. Ich habe 2u und 3u verwendet, was zu zwei unsigned int-Argumenten und einem signed int führt. Aufgrund der Anordnung der Parameter ist der Rückgabewert von add int. C++ Insights zeigt, dass dies zu einer impliziten Umwandlung in add führt. Ein weiterer Einblick, den C++ Insights uns zeigt.

Fold-Ausdrücke

Mit C++17 und Fold-Ausdrücken können wir unseren Code darauf reduzieren:

template<typename... ARGS>
auto add(const ARGS&... args)
{
return (args + ...);
}

int main()
{
return add(1, 2u, 3u);
}

Ich mag es wirklich, wie die neuen Standards dazu führen, dass wir weniger Code schreiben müssen. Das Ergebnis von C++ Insights ändert sich ebenfalls:

template<typename... ARGS>
auto add(const ARGS&... args)
{
return (args + ...);
}

/* First instantiated from: insights.cpp:9 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
unsigned int add<int, unsigned int, unsigned int>(const int & __args0, const unsigned int & __args1, const unsigned int & __args2)
{
return static_cast<unsigned int>(__args0) + __args1 + __args2;
}
#endif


int main()
{
return static_cast<int>(add(1, 2u, 3u));
}

Natürlich können wir auch verschiedene Klassen-Templates schreiben. Schauen wir uns dieses Codebeispiel an:

template<int...>
struct add;

template<>
struct add<>
{
static constexpr int value = 0;
};

template<int i, int... tail>
struct add<i, tail...>
{
static constexpr int value = i + add<tail...>::value;
};

static_assert(6 == add<1, 2, 3>::value, "Expect 6");

Wir haben ein variadisches Klassen-Template, das die Summe einer beliebigen Anzahl von Zahlen berechnet. In C++ Insights werden alle im Hintergrund stattfindenden Instanziierungen angezeigt, um das Ergebnis zu berechnen. Hier können wir sehen, wie jedes Mal eine Zahl erscheint, bis es keine mehr gibt:

template<int...>
struct add;

/* First instantiated from: insights.cpp:16 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct add<1, 2, 3>
{
inline static constexpr const int value = 1 + add<2, 3>::value;

};
#endif


/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct add<2, 3>
{
inline static constexpr const int value = 2 + add<3>::value;

};
#endif


/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct add<3>
{
inline static constexpr const int value = 3 + add<>::value;

};
#endif


template<>
struct add<>
{
static constexpr int value = 0;
};

template<int i, int... tail>
struct add<i, tail...>
{
static constexpr int value = i + add<tail...>::value;
};

/* PASSED: static_assert(6 == add<1, 2, 3>::value, "Expect 6"); */

Referenz auf ein Array

Eine weitere Sache von Templates ist, dass sie eine Referenz auf ein Array aufnehmen können. Das verhindert, dass ein Array zu einem Zeiger zerfällt:

template<typename T, int N>
void Rx(T (&data)[N])
{
// assuming char here
static_assert(sizeof(data) == 5);
}

int main()
{
char buffer[5]{};

Rx(buffer);
}

In C ++ Insights können wir sehen, dass die Instantiierung den Typ sowie die Größe des Arrays enthält:

template<typename T, int N>
void Rx(T (&data)[N])
{
// assuming char here
static_assert(sizeof(data) == 5);
}

/* First instantiated from: insights.cpp:12 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void Rx<char, 5>(char (&data)[5])
{
/* PASSED: static_assert(sizeof(data) == 5); */
}
#endif


int main()
{
char buffer[5] = {'\0', '\0', '\0', '\0', '\0'};
Rx(buffer);
}

Abgesehen von Templates, die wir in diesem Beispiel sehen können, sehen wir auch den Effekt der braced-Initialisierung von buffer. Der Compiler füllt alle Elemente des Arrays für uns mit dem Standardwert. Auf diese Weise können wir uns vom guten, alten memset verabschieden und unsere Programme schneller und sicherer machen.

Ich hoffe, ich konnte zeigen, wie C++ Insights hilfreich sein kann, wenn es um Templates geht. Für mich ist es ein wichtiges Instrument, wenn es um das Lehren und Erklären von Templates geht, insbesondere von variadischen Vorlagen.

Viel Spaß mit C++ Insights. Wer möchte, kann das Projekt unterstützen, entweder als Patreon oder natürlich auch mit Code-Beiträgen.

Demnächst mehr zu C++ Insights ... Der nächste Artikel beschäftigt sich mit Lambdas.