C++17: Kleinvieh macht auch Mist

Seite 2: Templates

Inhaltsverzeichnis

Für Klassen-Templates gibt es nun ein Feature, das es für Funktions-Templates schon lange gibt: Die Template-Parameter müssen nicht angegeben werden, wenn sie automatisch ermitteln werden können. In diesem Falll heißt das, wenn die Datentypen durch den Aufruf des Konstruktor klar werden. Beispielsweise genügt nun folgende Schreibweise:

std::tuple t(4, 3, 2.5);

Bisher war dafür folgender Code nötig:

std::tuple<int,int,double) t(4, 3, 2.5);

Ebenso funktionieren folgende Formulierungen:

std::lock_guard lg(mx);
std::shared_ptr up{new int(2)};

Damit erübrigen sich viele Hilfsfunktionen, die nur dazu dienten, die Datentypen der Klassen-Templates nicht unbedingt angeben zu müssen. So reicht jetzt pair(2, 4.5) statt make_pair(2, 4.5).

Hilfsfunktionen wie make_pair() sind dadurch aber nicht gänzlich überflüssig, denn der std::pair-Konstruktor führt kein "decay" durch (die Konvertierung von Arrays im Zeiger).

Der Ausdruck

std::pair("hi", "guy");

erzeugt also ein Objekt vom Typ

std::pair<const char[6], const char[7]>

während:

std::make_pair("hi","guy);

nach wie vor ein Objekt vom Typ

std::pair<const char*, const char*>

erstellt. Das kann beim Umgang mit verschiedenen Wertepaaren wichtig sein, da sich dadurch die Datentypen der einzelnen Wertepaare nicht unterscheiden.

Parameter von Value-Templates dürfen neuerdings mit dem Schlüsselwort auto deklariert werden. Es definiert, dass der Template-Parameter ein Objekt eines beliebigen Typs ist (im Gegensatz zu "Variadic Templates", bei denen die Template-Parameter die Datentypen selbst sind):

template <auto N>
class S {
...
};
S<42> s1; // OK: Datentyp von N in S ist int
S<'a'> s2; // OK: Datentyp von N in S ist char

Allerdings sind Gleitkomma-Datentypen als Template-Argumente nach wie vor nicht erlaubt:

S<2.5> s3;  // FEHLER

Partielle Spezialisierungen sind wie folgt möglich:

template <int N> class S<N> {
...
};

Auch hier sind Modifizierungen für auto erlaubt. Die folgende Definition legt fest, dass P ein konstanter Zeiger auf einen beliebigen Typ ist:

<const auto* P> struct S;

Entwickler können das Sprachmittel insbesondere dazu einsetzen, eine heterogene Liste beliebig vieler Template-Werte-Parameter zu definieren:

template <auto... vs>
struct value_list {
...
};

Mit einem kleinen Trick können sie auf die Weise eine homogene Liste von Template-Werte-Parametern definieren, deren gemeinsamer Datentyp sie noch nicht festlegen:

template <auto v1, decltype(v1)... vs>
struct typed_value_list {
...
};

Bei einer Definition, die beliebig viele Template-Argumente abgearbeitet hat, brauchte man bisher immer ein separates Abschlusskriterium:

void print ()
{
}
template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args)
{
std::cout << firstArg << std::endl;
print(args...);
}

In C++17 ist stattdessen folgende Implementierung möglich:

template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args)
{
std::cout << firstArg << std::endl;
if constexpr(sizeof...(args) > 0) {
print(args...);
}
}

if constexpr wird zur Compile-Zeit ausgewertet, sodass der Compiler die dazugehörigen then- oder else-Anweisungen gar nicht erst übersetzt, wenn die Bedingung beim Kompilieren false beziehungsweise true liefert.

Das Feature wird historisch bedingt übrigens "constexpr if" genannt, obwohl sich die Reihenfolge der Schlüsselworte seit dem initialen Vorschlag umgekehrt hat.