C++20: Concepts – was wir nicht bekommen

Modernes C++  –  0 Kommentare

Die Template Introduction des Concepts TS (Technical Spezification) stellt eine neue Art dar, Concepts anzuwenden. Die syntaktische Variante ist nicht Bestandteil des Concepts Draft und damit auch nicht des C++20 -Standards. Ich treffe aber keine Annahmen darüber, was die nicht so nahe Zukunft bringen wird.

Template Introduction

Diesen Artikel möchte ich mit einem kleinen Rätsel beginnen. Welche der folgenden syntaktischen Variationen ist mit dem Concepts Draft nicht möglich?

template<typename T>                                  // (1)
requires Integral<T>
T gcd(T a, T b){
if( b == 0 ) return a;
else return gcd(b, a % b);
}

template<typename T> // (2)
T gcd1(T a, T b) requires Integral<T>{
if( b == 0 ){ return a; }
else return gcd(b, a % b);
}

template<Integral T> // (3)
T gcd2(T a, T b){
if( b == 0 ){ return a; }
else return gcd(b, a % b);
}

Integral auto gcd3(Integral auto a, Integral auto b){ // (4)
if( b == 0 ){ return a; }
else return gcd(b, a % b);
}

Integral{T} // (5)
Integral gcd(T a, T b){
if( b == 0 ){ return a; }
else{
return gcd(b, a % b);
}
}

Ich nehme an, du kennst nicht jede dieser Techniken. Unter diesen Namen sind sie bekannt:

  1. Requires Clause
  2. Trailing Requires Clause
  3. Constrained Template Parameters
  4. Abbreviated Function Templates
  5. Template Introduction

Welche Variante wird in dem Concepts Draft nicht unterstützt? Ich denke, die obskurste Form hat die Wahl gewonnen: Template Introduction. Stimmt! Eventuell erhalten wir diese Variante mit einem späteren C++-Standard. Ich bin aber sehr skeptisch. In meinen persönlichen Gesprächen hörte ich kein Wehklagen, dass diese Variante kein Bestandteil von C++20 sei. Ich bin auch kein großer Fan der Template Introduction, denn sie führt eine Asymmetrie ein. Ich bin kein Freund von Asymmetrien.

Die Asymmetrie

Statt ein eingeschränktes Template mit template<typename Integral> zu erklären, erlaubt es Template Introduction direkt mit Integral{T}. Hier ist schon die Asymmetrie. Template Introduction lässt sich nur mit Concepts (constrained placeholders), aber nicht mit auto (unconstrained placeholders) verwenden. Das folgende Beispiel bringt die Asymmetrie auf den Punkt. Dieses und das nächste Beispiel basieren auf der vorläufigen Concepts-TS-Spezifikation und sind mit dem GCC übersetzt:

// templateIntroduction.cpp

#include <type_traits>
#include <iostream>

template<typename T>
concept bool Integral(){
return std::is_integral<T>::value;
}

Integral{T} // (1)
Integral gcd(T a, T b){
if( b == 0 ){ return a; }
else{
return gcd(b, a % b);
}
}

Integral{T} // (2)
class ConstrainedClass{};

/*

auto{T} // (4)
auto gcd(T a, T b){
if( b == 0 ){ return a; }
else{
return gcd(b, a % b);
}
}

auto{T} // (5)
class ConstrainedClass{};

*/


int main(){

std::cout << std::endl;

auto res= gcd(100, 10);

ConstrainedClass<int> constrainedClass;
ConstrainedClass<double> constrainedClass1; // (3)

std::cout << std::endl;

}

Ich wende Template Introduction bei dem Funktions-Template gcd (Zeile 1) und dem Klassen-Template (Zeile 2) an. Wie erwartet, meldet sich das Concept zur Compilezeit, da ConstrainedClass (Zeile 3) nicht instanziiert werden kann.

Die Asymmetrie besteht darin, dass ich nicht einfach Integral durch auto in den Zeilen 4 und 5 ersetzen kann. Bis zum jetzigen Zeitpunkt zog es sich wie ein roter Faden durch meine Artikel zu Concepts: Concepts (constrained placeholders) können dort eingesetzt werden, wo auto (unconstrained placeholders) erlaubt ist. Dies gilt auch in die andere Richtung. Dieses einfache Prinzip gilt mit Template Introduction nicht mehr.

Natürlich kann ich diese Restriktion überwinden, indem ich ein Concept definiere, dass immer true zurückgibt:

// templateIntroductionGeneric.cpp

#include <iostream>
#include <string>
#include <typeinfo>
#include <utility>

struct NoDefaultConstructor{ // (5)
NoDefaultConstructor() = delete;
};

template<typename T> // (1)
concept bool Generic(){
return true;
}

Generic{T} // (2)
Generic gcd(T a, T b){
if( b == 0 ){ return a; }
else{
return gcd(b, a % b);
}
}

Generic{T} // (3)
class ConstrainedClass{
public:
ConstrainedClass(){
std::cout << typeid(decltype(std::declval<T>())).name() // (4)
<< std::endl;
}
};


int main(){

std::cout << std::endl;

std::cout << "gcd(100, 10): " << gcd(100, 10) << std::endl;

std::cout << std::endl;

ConstrainedClass<int> genericClassInt;
ConstrainedClass<std::string> genericClassString;
ConstrainedClass<double> genericClassDouble;
ConstrainedClass<NoDefaultConstructor> genericNoDefaultConstructor;

std::cout << std::endl;

}

Generic in Zeile 1 ist ein Concept, dass für alle Datentypen true zurückgibt. Nun kann ich die Syntax wieder vereinheitlichen und ein uneingeschränktes Funktions-Template (Zeile 2) und ein uneingeschränktes Klassen-Template (Zeile 3) definieren. Ehrlich gesagt sind die Ausdrücke uneingeschränkte Funktions-Template oder Klassen-Templates keine offiziellen Begriffe. Ich habe sie der Einfachheit halber eingeführt.

Der Ausdruck typeid(decltype(std::declval<T>())).name() in Zeile 4 löst vermutlich ein wenig Unbehagen aus. std::declvar<T> (C++11) konvertiert den Typ-Parameter T in einen Referenztyp. Dank dieses Referenztyps lässt sich jede Memberfunktion von T aufrufen, ohne dass dazu T erzeugt werden muss. Dies gilt selbst für Datentypen T, die wie in Zeile 5 keinen Defaultkonstruktor besitzen. In meinem Beispiel (Zeile 4) verwende ich den Konstruktor auf dem Referenztyp um seine String-Repräsentierung zu erhalten. Hier ist des Ausgabe des Programms mit GCC:

Wie geht's weiter?

Auf ein mächtiges Feature von Concepts bin ich noch nicht eingegangen: die Definition von Concepts. Meist ist dies nicht notwendig, da du in den meisten Fällen das Rad neu erfinden willst. C++20 hat bereits viele Concepts vordefiniert. Ich werde in meinem nächsten Artikel die vordefinierten Concepts vorstellen und zeigen, wie sich eigene Concepts definieren lassen.

Dies ist mein letzter Artikel für 2019. Ich veröffentliche meinen nächsten Artikel am 12. Januar 2020. Wenn deine hoffentlich besinnliche Zeit zu besinnlich wird, kann ich nur auf meine Übersicht zu den 300 Artikeln meines Blogs Modernes C++ verweisen.