Template-Metaprogrammierung oder constexpr, eine Einführung und ein Vergleich in C++17, Teil 2
In C++ sind SFINAE (Substitution Failure Is Not An Error) und constexpr zwei wichtige Konzepte, die eine Schlüsselrolle bei der Metaprogrammierung von Vorlagen bzw. der Auswertung zur Kompilierungszeit spielen. Beide ermöglichen die Erstellung von leistungsfähigerem und effizienterem Code, haben jedoch unterschiedliche Anwendungsfälle und Einschränkungen. In diesem Artikel werden wir diese Konzepte ausführlich erläutern, Codebeispiele bereitstellen und ihre Stärken und Schwächen vergleichen und gegenüberstellen.
SFINAE
SFINAE ist ein Mechanismus, der es einem Compiler ermöglicht, Funktionen aus dem Überladungssatz auszuschließen, je nachdem, ob ein bestimmter Ausdruck ausgewertet werden kann oder nicht. Beispielsweise können wir SFINAE verwenden, um eine Vorlagenfunktion zu erstellen, die die Fakultät einer Zahl berechnet, jedoch nur für ganzzahlige Typen:
In diesem Code verwenden wir std::enable_ifdie Funktionsvorlage zum bedingten Aktivieren oder Deaktivieren, je nachdem, ob Tes sich um einen Integraltyp handelt. Wenn Tes nicht ganzzahlig ist, std::enable_ifführt dies zu einem Ersetzungsfehler und der Compiler entfernt diese Funktion aus dem Überladungssatz. Dadurch wird verhindert, dass beim Kompilieren Fehler auftreten, da die Funktion nicht instanziiert wird.
Wir können SFINAE auch verwenden, um komplexere Vorlagenfunktionen zu erstellen, die von mehreren Bedingungen abhängen. Beispielsweise können wir eine Vorlagenfunktion erstellen, die nur Typen akzeptiert, die über eine oder mehrere spezifische Mitgliedsfunktionen verfügen. Bevor wir dies tun, müssen wir lediglich das Dienstprogramm std::declval verstehen . Es ermöglicht die Erstellung temporärer Objekte eines bestimmten Typs ohne einen Standardkonstruktor. Beispiel von cppreference.com , das ich in den Compiler-Explorer eingefügt habe :
Wie oben sehen Sie, dass NonDefaultin der Struktur der Standarddestruktor gelöscht wurde! Wir möchten vergleichen, dass die Typen der letzteren und der Strukturrückgabe Defaultbei der Ausführung der Methode gleich sind foo. Defaulthat einen impliziten Standard-Destruktor, also erstellen wir einen temporären Defaultund rufen fooihn auf. Wir übergeben das an decltype , um den Typ abzuleiten und eine Variable n1 dieses Typs (int) als gleich 1 zu deklarieren. Jetzt möchten wir prüfen, ob fooin der NonDefault-Struktur derselbe Typ zurückgegeben wird, da dieser jedoch keinen Standardkonstruktor hat, wie wir Wie oben besprochen, greifen wir hier auf std::declval zurück, das uns eine R-Wert-Referenz zurückgibtNonDefaultund ermöglichen den Aufruf von foo für das temporäre Objekt. Das Gleiche machen wir dann mit std::decltype und weisen die Variable n1unserer neu deklarierten Variablen zu n2. Für beide Variablen werden std::coutdie Werte 1 ausgegeben.
Kommen wir nun zum Beispiel selbst mit std::enable_if , das prüft, ob zwei Methoden aufgerufen wurden begin()und end():
In diesem Code verwenden wir eine Methode, mit der man temporäre Objekte des Typs ohne Standardkonstruktor std::declvalerstellen und den Typ ihrer und Mitgliedsfunktionen bestimmen kann. Wir müssen decltype verwenden , um declval verwenden zu können , andernfalls ist dies nicht zulässig ...Tdecltypebegin()end()
Wir prüfen dann std::is_same, ob diese Typen gleich sind. Wenn dies der Fall ist, wird die Funktionsvorlage aktiviert, andernfalls wird sie aus dem Überladungssatz ausgeschlossen.
Eine Einschränkung von SFINAE besteht darin, dass es nur während der Ableitung von Vorlagenargumenten funktioniert, was bedeutet, dass es nicht zum bedingten Ausschließen von Funktionen verwendet werden kann, nachdem der Überladungssatz bereits erstellt wurde. Hier if constexprkommt es ins Spiel.
wenn constexpr
if constexprist eine neue Funktion, die in C++17 eingeführt wurde und es uns ermöglicht, Code zur Kompilierungszeit basierend auf einem konstanten Ausdruck zur Kompilierungszeit bedingt auszuführen. Im Gegensatz zu SFINAE if constexprkann es verwendet werden, um Code vollständig von der Kompilierung auszuschließen, selbst nachdem der Überladungssatz erstellt wurde.
Beispielsweise können wir damit if constexpreine Vorlagenfunktion erstellen, die die Fakultät einer Zahl berechnet, allerdings nur für ganzzahlige Typen:
In diesem Code verwenden wir if constexprdie bedingte Ausführung von Code basierend darauf, ob Tes sich um einen integralen Typ handelt. Wenn Tes ganzzahlig ist, führt die Funktion die Faktorberechnung aus. Andernfalls schlägt eine statische Behauptung fehl, wodurch die Kompilierung des Codes verhindert wird. Beachten Sie, dass dies std::is_integral_veine Abkürzung für ist std::is_integral<T>::value.
Wir können damit auch if constexprkomplexere Vorlagenfunktionen erstellen, die von mehreren Bedingungen abhängen. Beispielsweise können wir eine Vorlagenfunktion erstellen, die nur Typen akzeptiert, die sowohl eine begin()als auch end()eine Memberfunktion haben, wie wir es oben mit std::enable_if gemacht haben :
Im obigen Codebeispiel erstellen wir ein Typmerkmal has_begin_end, das prüft, ob ein Typ sowohl über Memberfunktionen als auch Tverfügt . Wir verwenden es , um den Typ in einen Referenztyp umzuwandeln, der es uns ermöglicht, Mitgliedsfunktionen innerhalb von Ausdrücken aufzurufen, ohne wie zuvor beschrieben ein Objekt erstellen zu müssen. Zuerst rufen wir die Mitgliedsfunktion on auf , und wenn das gelingt, verwerfen wir das Ergebnis mit dem Komma-Operator. Als nächstes versuchen wir, on aufzurufen . Bei Erfolg erhalten wir den Rückgabetyp using . Beachten Sie, dass dies ein Muss ist, da Memberfunktionen für Referenztypen nur mit inside aufgerufen werden können . Wenn das alles gelingt, kehren wir schließlich zurückbeginendstd::declvalTdecltypebegin()std::declval<T>()end()std::declval<T>()decltypedecltypedeclvaldecltypevoid_tvoid, andernfalls können die Vorlagenparameter von void_tnicht ausgewertet werden und die Spezialisierung kann während der Namenssuche nicht aufgelöst werden.
Ein allgegenwärtiges Beispiel, das wir auch verwenden können, um die Verwendung von S FINAE und constexpr gegenüberzustellen , ist die Implementierung des faktoriellen Algorithmus
Beispiel mit SFINAE
Im obigen Code verwenden wir SFINAE, um nicht ganzzahlige Typen aus dem Überladungssatz der factorialFunktion auszuschließen, indem wir std::enable_ifmit verwenden, um std::is_integral zu validieren, dass es sich um eine Zahl handelt.
Beispiel mit if constexpr
Wir verwenden es if constexpr, um den Fakultätsberechnungscode bedingt auszuführen, wenn Tes sich um einen Integraltyp handelt. Wenn Tes sich nicht um einen integralen Typ handelt, schlägt die Funktion mit statischer Assertion fehl, wodurch verhindert wird, dass der Code kompiliert wird.
Anwendungsfälle und Empfehlungen
SFINAE und if constexpr sind beide leistungsstarke Tools, mit denen sich effizienterer und aussagekräftigerer Code erstellen lässt. Sie haben jedoch unterschiedliche Anwendungsfälle und Einschränkungen.
SFINAE wird am besten verwendet, wenn Sie Funktionen basierend auf den Eigenschaften der Funktionsargumente bedingt aus dem Überladungssatz ausschließen möchten. Mit SFINAE können Sie beispielsweise Funktionsvorlagen erstellen, die nur bestimmte Typen akzeptieren oder über bestimmte Eigenschaften verfügen. SFINAE ist auch nützlich, wenn Sie komplexere Vorlagenfunktionen erstellen möchten, die von mehreren Bedingungen abhängen.
if constexprwird hingegen am besten verwendet, wenn Sie Code zur Kompilierungszeit basierend auf konstanten Ausdrücken zur Kompilierungszeit bedingt ausführen möchten. if constexprist besonders nützlich, wenn Sie prägnanteren und lesbareren Code erstellen möchten, da die Bedingung Teil des Funktionskörpers ist und nicht über die Vorlagenparameter verteilt ist. if constexprist auch nützlich, wenn Sie informativere Fehlermeldungen bereitstellen möchten, da statische Zusicherungen verwendet werden können, um detailliertere Informationen bereitzustellen.
Berücksichtigen Sie bei der Entscheidung, ob SFINAE oder if constexpr verwendet werden soll , Folgendes:
- Verwenden Sie SFINAE, wenn Sie Funktionen basierend auf den Eigenschaften der Funktionsargumente bedingt ausschließen möchten.
- Verwenden Sie if constexpr , wenn Sie Code basierend auf konstanten Ausdrücken zur Kompilierungszeit bedingt ausführen möchten.
- Verwenden Sie SFINAE, wenn Sie komplexere Vorlagenfunktionen erstellen möchten, die von mehreren Bedingungen abhängen.
- Verwenden Sie if constexpr, wenn Sie prägnanteren und lesbareren Code erstellen oder informativere Fehlermeldungen bereitstellen möchten.
- Berücksichtigen Sie die Einschränkungen jedes Ansatzes, z. B. die Tatsache, dass SFINAE nur während der Ableitung von Vorlagenargumenten funktioniert oder dass für constexpr C++17-Unterstützung erforderlich ist.
SFINAE durch die Verwendung von std::enable_if und if constexpr sind zwei wichtige Konzepte in C++, die die Erstellung von effizientem und ausdrucksstarkem Code ermöglichen. SFINAE wird am besten verwendet, wenn Sie Funktionen basierend auf den Eigenschaften der Funktionsargumente bedingt ausschließen möchten, während constexpr am besten verwendet wird, wenn Sie Code basierend auf konstanten Ausdrücken zur Kompilierungszeit bedingt ausführen möchten. Wir haben gelernt, dass wir declval mit delctype verwenden können, um die Existenz bestimmter Memberfunktionen zu überprüfen, wenn dem Typ kein Standardkonstruktor fehlt. Durch das Verständnis der Unterschiede zwischen SFINAE und if constexprWenn Sie Ansätze und ihre jeweiligen Stärken und Schwächen kennen, können Sie beim Schreiben von Vorlagenfunktionen bessere Entscheidungen treffen und effizienteren und aussagekräftigeren Code erstellen.
Hinweis: Überprüfen Sie Teil 1https://medium.com/@joao_vaz/template-meta-programming-in-c-17-primer-1b493a22d51
Bild von <a href=“https://www.freepik.com/free-vector/business-decisions-concept-illustration_11392273.htm#query=choice&position=26&from_view=search&track=sph">Bild von storyset</a> auf Freepik

![Was ist überhaupt eine verknüpfte Liste? [Teil 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































