Vor einger Zeit bin ich über eine sehr interessante (und für mich neue) Technik namens „Design by Introspection“ gestoßen, welche mit dem Concepts-Feature von C++20 sehr einfach anwendbar ist.
Diese Technik kann mit dem folgendem kleinen Code-Schnipsel veranschaulicht werden:
auto value( auto v )
{
// unnamed concept in constexpr if-branch!
if constexpr ( requires { v.value; } ) { // if type of v has a .value use this branch.
return v.value;
} else {
return v;
}
}
Zunächst, der Parameter der Funktion hat den Typ auto
. Dadurch wird er während des Kompilierens für jeden Aufruf der Funktion jeweils zu einem konkreten Typ gesetzt.
Der interessante Teil ist jetzt in Zeile 4. Wenn der Code v.value
für den konkreten Typ gültig und kompilierbar ist, dann wird der If-Zweig aus Zeile 4 genommen und der Return-Wert ist v.value
. Andernfalls wird v
von dem Else-Zweig zurückgegeben.
Zeile 4 nutzt einen „requires“-Ausdruck im constexpr-if. Dieser bildet ein constraint, welches zur Compile-Zeit geprüft wird. Und weil der Funktions-Parameter vom Typ auto
ist, verhält sich die Funktion wie eine Template-Funktion in der SFINAE angewendet wird bzw. vorhanden ist (SFINAE = Substitution Failure Is Not An Error).
Wenn der Typ von v kein gültiges v.value
hat, dann sorgt SFINAE dafür, dass kein Compile-Fehler auftritt, sondern stattdessen der Else-Zweig verwendet wird.
Die obige Beispiel Funktion könnte man dann so aufrufen:
long long number = 123;
auto res = value( number ); // the v branch is used.
struct X {
bool x = false;
std::string name = "foo";
long long value = 42;
} my_x;
auto res2 = value( my_x ); // the v.value branch is used.
Ich denke, mit “Design by Introspection” ist ein großartiger Weg für „Generic Programming“ in C++20 vorhanden. Man kann sich Tonnen an Anwendungsfällen vorstellen. Vielleicht kommt hierzu noch mehr in zukünftigen Blog Posts.