constexpr if

Im heutigen Artikel möchte ich ein sehr interessantes C++17-Feature vorstellen: constexpr if. Es ermöglicht, Sourcecode bedingt zu kompilieren und kann auch für nette Tricks zur Compilezeit verwendet werden.

In Pocket speichern vorlesen Druckansicht 105 Kommentare lesen
Lesezeit: 6 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Im heutigen Artikel möchte ich ein sehr interessantes C++17-Feature vorstellen: constexpr if. Es ermöglicht , Sourcecode bedingt zu kompilieren und kann auch für nette Tricks zur Compilezeit verwendet werden.

Die Einführung von constexpr if ist recht einfach.

template <typename T>
auto getValue(T t) {
if constexpr (std::is_pointer_v<T>) // (1)
return *t; // deduces return type to int for T = int*
else // (2)
return t; // deduces return type to int for T = int
}

Der Codeschnipsel zeigt eine interessante Eigenschaft von constexpr if: Obwohl es constexpr if genannt wird, wird es als if constexpr verwendet: if constexpr (std::is_pointer_v<T>).

Wenn T ein Zeiger ist, wird der if-Zweig in (1) kompiliert. Wenn nicht, wird der else-Zweig in (2) kompiliert. Zwei Punkte sind wichtig zu erwähnen: Die Funktion getValue hat zwei verschiedene Rückgabetypen und beide Zweige der if-Anweisung müssen gültig sein.

Der Ausdruck in constexpr if muss ein Compilezeitprädikat sein: eine Funktion, die einen booleschen Wert zurückgibt und zur Compilezeit ausgeführt wird. Ich habe im Codeschnipsel eine Funktion aus der type-traits-Bibliothek verwendet. Alternativ lässt sich in C++20 auch ein Concept verwenden. Hier ist das entsprechende Beispiel, das das Concept std::integral einsetzt:

template <typename T>
auto get_value(T t) {
if constexpr (std::integral<T>) // (1)
return *t; // deduces return type to int for T = int*
else // (2)
return t; // deduces return type to int for T = int
}

Okay, die beiden Codeschnipsel sind nicht sehr beeindruckend. Daher gehe ich über zur Template-Metaprogrammierung.

Dank constexpr if ist Template-Metaprogrammierung oft einfacher zu schreiben und zu lesen.

Metaprogrammierung ist Programmieren auf Programmen. C++ wendet Metaprogrammierung zur Compilezeit an. Sie begann in C++98 mit der Template-Metaprogrammierung, wurde in C++11 mit der type-traits-Bibliothek formalisiert und hat sich seitdemstetig verbessert.

Hier ist das "Hello World" der Template-Metaprogrammierung: Berechnen der Fakultät einer Zahl:

// factorial.cpp

#include <iostream>

template <int N> // (2)
struct Factorial{
static int const value = N * Factorial<N-1>::value;
};

template <> // (3)
struct Factorial<1>{
static int const value = 1;
};

int main(){

std::cout << '\n';

std::cout << "Factorial<5>::value: "
<< Factorial<5>::value << '\n' ; // (1)
std::cout << "Factorial<10>::value: "
<< Factorial<10>::value << '\n' ; // (4)

std::cout << '\n' ;

}

Der Aufruf factorial<5>::value (1) bewirkt die Instanziierung des primären oder allgemeinen Template (2). Während dieser Instanziierung wird Factorial<4>::value instanziiert. Diese rekusrive Instanziierung endet mit dem vollständig spezialisierte Klassen-Template Factorial<1> (Zeile 3).

Mehr über Template-Metaprogrammierung findet sich in meinen früheren Artikeln:

Nun schreibe ich das Programm mithilfe von constexpr if um:

// factorialConstexprIf.cpp

template <int N> // (1)
struct Factorial{
static int const value = N * Factorial<N-1>::value;
};

template <> // (2)
struct Factorial<1>{
static int const value = 1;
};

template <int N> // (3)
constexpr int factorial() {
if constexpr (N >= 2)
return N * factorial<N-1>();
else
return N;
}

int main(){

static_assert(Factorial<5>::value == factorial<5>()); // (4)
static_assert(Factorial<10>::value == factorial<10>()); // (4)

}

Das primäre Template von Factorial (Zeile 1) wird zur if-Bedingung in der constexpr-Funktion factorial (3), und die vollständige Spezialisierung von Factorial für 1 (2) wird zum else-Fall in der constexpr-Funktion factorial (3). Natürlich geben die Klassen-Templates Factorial und die constexpr-Funktion factorial das gleiche Ergebnis zurück und werden zur Compilezeit ausgeführt (4). Um es kurz zu machen: Ich bevorzuge die constexpr Funktion mit constexpr if, weil sie sich so angenehm liest wie eine normale Funktion.

Zum Abschluss möchte ich noch ein weiteres Beispiel zeigen: die berüchtigte Fibonacci-Funktion. Die folgenden Implementierungen basieren auf Template-Metaprogrammierung (Fibonacci) und constexpr if (fibonacci).

// fibonacciConstexprIf.cpp

template<int N>
constexpr int fibonacci()
{
if constexpr (N>=2)
return fibonacci<N-1>() + fibonacci<N-2>();
else
return N;
}

template <int N> // (1)
struct Fibonacci{
static int const value =
Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};

template <> // (2)
struct Fibonacci<1>{
static int const value = 1;
};

template <> // (3)
struct Fibonacci<0>{
static int const value = 0;
};

int main() {

static_assert(fibonacci<7>() == 13);
static_assert(fibonacci<7>() == Fibonacci<7>::value);

}

Die constexpr-Funktion fibonacci ist sehr angenehm zu lesen. Die gesamte Funktionalität befindet sich in einem einzigen Funktionskörper. Im Gegensatz dazu benötigt das Template-Metaprogramm Fibonacci drei Klassen. Das primäre Template (1) und die beiden vollständigen Spezialisierungen für die Werte 1 und 0 (2 und 3).

I created the platform for my new mentoring on https://www.modernescpp.org/. You can skip through each of the 28 lessons. I also presented the 6th lesson about move semantics and perfect forwarding in the post 'More Information about my Mentoring Program "Fundamentals for C++ Professionals"'. Here are the next step before I start the mentoring program.

  • Beginning of March: online information session about my mentoring program, where you can also ask your questions
  • Middle of March: my mentoring program opens for registration
  • April: the registration for the mentoring program closes, and the mentoring program starts

If you want to stay informed, write an e-mail to info@ModernesCpp.de with the subject "Mentoring". Write me also an e-mail if you need more information

Templates sind ein mächtiges Werkzeug und bieten daher neue Möglichkeiten für den Softwareentwurf. In meinem nächsten Artikel schreibe ich über statische und dynamische Polymorphie. ()