C ++ 17 vs. C ++ 14 - if-constexpr

Wir freuen uns, dass if-constexpr es in C ++ 17 geschafft hat. Sie können es selbst mit dem aktuellen clang-Trunk versuchen.

In diesem Blog-Beitrag gehen wir noch einmal auf C ++ 14-Code ein und versuchen, die neue Funktion zu nutzen.

Wenn Sie nicht über if-constexpr verfügen, müssen Sie häufig auf ausgefeilte Metaprogrammiertechniken zurückgreifen, bei denen Template-Pattern-Matching, Überladungsauflösung und SFINAE zum Einsatz kommen.

Beispiel 1 - das n-te Argument erhalten

Viele Template-Metaprogramme arbeiten mit verschiedenen Typenlisten. In C ++ 14 wird das Abrufen des n-ten Typs einer Argumentliste häufig folgendermaßen implementiert:

template 
struct Arg {
 Vorlage 
 constexpr auto operator () (X x, Xs… xs) {
   return Arg  {} (xs…);
 }
};
Vorlage <>
struct Arg <0> {
 Vorlage 
 constexpr auto operator () (X x, Xs…) {
   return x;
 }
};
template 
constexpr auto arg = Arg  {};
// arg 2 (0,1,2,3,4,5) == 2;

C ++ 17 macht dies etwas intuitiver:

template 
struct Get {
 Vorlage 
 constexpr auto operator () (X x, Xs… xs) {
   wenn constexpr (n> sizeof… (xs)) {
     Rückkehr;
   } sonst wenn constexpr (n> 0) {
     return Get  {} (xs…);
   } else {
     return x;
   }
 }
};

Beispiel 2 - API - Shimming

Manchmal möchten Sie eine alternative API unterstützen. Mit C ++ 14 können Sie auf einfache Weise überprüfen, ob ein Objekt auf eine bestimmte Weise verwendet werden kann:

Vorlage 
constexpr auto unterstütztAPI (T x) -> decltype (x.Method1 (), x.Method2 (), true_type {}) {
 Rückkehr {};
}
constexpr auto unterstütztAPI (…) -> false_type {
 Rückkehr {};
}

Das Implementieren von benutzerdefiniertem Verhalten in C ++ 14 kann folgendermaßen erfolgen:

Vorlage 
auto compute (T x) -> decltype (enable_if_t  {}) {
 return x.Method ();
}
Vorlage 
auto compute (T x) -> decltype (enable_if_t  {}) {
 return 0;
}

C ++ 17:

Vorlage 
int compute (T x) {
 if constexpr (supportedAPI (T {})) {
   // wird nur kompiliert, wenn die Bedingung wahr ist
   return x.Method ();
 } else {
   return 0;
 }
}

Dies ist sehr praktisch, da semantisch zusammengehöriger Code nicht über mehrere Funktionen verteilt ist. Darüber hinaus können Sie Lambdas definieren, die if-constexpr enthalten.

Beispiel 3 - Algorithmusauswahl zur Kompilierungszeit

Oft müssen Sie den besten Algorithmus basierend auf Regeln und Eigenschaften eines Typs finden. Es gibt viele Lösungen. Beispielsweise verwendet die STL TypeTags, um den richtigen Algorithmus für bestimmte Iteratoren auszuwählen.

struct FooTag {};
struct BarTag {};
auto foldFF (…) {}
auto foldFB (…) {}
auto foldBF (…) {}
auto foldBB (…) {}
Struktur A {
 / *… * /
 using tag = FooTag;
};
Struktur B {
 / *… * /
 using tag = BarTag;
};
Vorlage 
automatische Faltung (L 1, R r, FooTag, BarTag) {foldFB (l, r); }
/ * Weitere Versandfunktionen * /
Vorlage 
automatische Faltung (L l, R r) {
 Rückfalte (l, r,
 Typname L :: Tag {},
 Typenname R :: Tag {});
}

Wenn Sie jedoch komplexere Regeln haben, benötigen Sie möglicherweise eine leistungsfähigere Lösung - SFINAE:

C ++ 14:

struct BazTag: FooTag, BarTag {};
Vorlage  :: value &&
 is_base_of  :: value
> fold (L l, R r) {
 return foldFB (l, r);
}

Mit C ++ 17 können Sie diese Regeln mit weniger Boilerplate und klarer beschreiben:

Vorlage 
automatische Faltung (L l, R r) {
 using lTag = typename L :: tag;
 using rTag = typename R :: tag;
if constexpr (is_base_of  :: value) {
 if constexpr (is_same  :: value) {
   return foldFB (l, r);
 } else {
   return foldBB (l, r);
 } else {
   return foldFF ();
 }
}

Dies ist sehr praktisch, da die Arbeit mit ifs intuitiver ist als die Verwendung einer Vielzahl von Sprachfunktionen.

Refactoring von Metafunktionen wird so einfach wie gewöhnlicher Code. Mit if-constexpr gehört die Sorge um mehrdeutige Überlastungen und andere unerwartete Komplikationen der Vergangenheit an.

Wir werden unseren Compiler aktualisieren, sobald Clang 3.9 stabil ist.