Concepts Lite

Inhaltsverzeichnis[Anzeigen]

Wir bleiben im Jahr 2020. Mit C++20 werden wir Concepts Lite erhalten. Auch wenn verlässliche Aussagen über die Zukunft schwierig sind, so besitzt diese Aussage doch eine sehr hohe Wahrscheinlichkeit einzutreffen. Sie stammt aus dem Munde von Bjarne Stroustrup (Meeting C++ 2016).

Die klassischen Concepts

Die zentrale Idee der generischen Programmierung mit Templates in C++ ist es, Funktionen und Klassen zu definieren, die mit verschiedenen Typen verwendet werden können. Oft kommt es aber vor, dass ein Template mit einem ungeeigneten Typ instanziiert wird. Das Ergebnis sind gerne seitenlange, kryptische Fehlermeldungen zur Compilezeit, für die Templates traurige Berühmtheit erlangt haben. Daher waren Concepts als eines der wichtigsten Feature für den C++11 Standard geplant. Sie sollten es erlauben, Anforderungen an Templates zu stellen, die vom Compiler verifiziert werden. Im Juli 2009 wurden sie aber im wesentlichen aufgrund ihrer Komplexität aus dem Standard entfernt. "The C++0x concept design evolved into a monster of complexity." (Bjarne Stroustrup)

Concepts Lite


Mit C++20 erhält C++ Concepts Lite. Auch wenn Concepts Lite in der ersten Umsetzung vereinfachte Concepts sind, haben sie sehr viel zu bieten. Sie

  • erlauben Programmierern direkt die Anforderungen an die Templates als Teil des Interfaces zu formulieren.
  • unterstützen das Überladen von Funktionen und die Spezialisierung von Klassen-Templates, basierend auf den Anforderungen an die Templates.
  • erzeugen deutlich verbesserte Fehlermeldungen, indem sie die Anforderungen an die Template-Parameter mit den aktuellen Template-Argumenten vergleichen.

Diesen deutlichen Mehrwert gibt es ohne eine längere Übersetzungs- oder Laufzeit des Programms. Inspiriert sind Concepts Lite durch Haskell's Typklassen. So werden Concepts Lite semantische Kategorien und nicht syntaktische Einschränkungen beschreiben. Für Typen werden dies Kategorien wie DefaultConstructible, MoveConstructible, CopyConstructible, MoveAssignable, CopyAssignable oder auch Destructible sein. Für Container werden dies Kategorien wie ReversibleContainer, AllocatorAwareContainer, SequenceContainer, ContinousContainer, AssociativeContainer oder UnorderedAssociativeContainer sein. Einen genauen Überblick über Concepts Lite gibt die Webressource cppreference.com.

Bevor ich Concepts Lite genauer vorstelle, ist ein Blick auf Haskells Typklassen sehr interessant.

Typklassen in Haskell

Typklassen sind Interfaces für ähnliche Typen. Ist ein Typ Mitglied einer Typklasse, so bietet dieser Typ bestimmte Eigenschaften an. Typklassen erfüllen für die generische Programmierung eine ähnliche Rolle, wie Interfaces für die objektorientierte Programmierung. Hier ist ein Teil der Typklassenhierachie Haskell's.

typeclass

Was zeichnet nun Typen aus, die Mitglied der Typklasse Eq sind. Eq steht in diesem Fall für Gleichheit (eng. Equality) und fordert von seinen Mitgliedern:

 

class Eq a where 
    (==) :: a -> a -> Bool 
    (/=) :: a -> a -> Bool 
    a == b = not (a /= b) 
    a /= b = not (a == b) 

Eq fordert, dass seine Typen eine Funktion Gleichheit (==) und Ungleichheit (/=) implementieren. Der Ausdruck a -> a -> Bool beschreibt die Signatur der Funktion. Die Funktion erwartet zwei gleiche Typen a und gibt einen Wahrheitswert Bool zurück. Tatsächlich reicht es aber aus, für einen Typ Gleichheit oder Ungleichheit zu definieren, da Gleichheit durch Ungleichheit, Ungleichheit durch Gleichheit definiert werden kann. Die Defaultimplemetierung für Gleichheit und Ungleichheit sind bereits in den letzten zwei Zeilen vorhanden.

Durch den folgenden Codeschnipsel wird der built-in Typ Bool zur Instanz der Typklasse Eq.

instance Eq Bool where 
    True == True = True 
    False == False = True 
    _ == _ = False 

 

Haskells Typklassen bilden eine Hierarchie. Die Typklasse Ord ist eine Unterklasse der Typklasse Eq. Instanzen der Typklasse Ord müssen daher Mitglieder der Typklasse Eq sind und darüber hinaus die Vergleichsoperatoren unterstützen.

Für einige Typklassen kann Haskell bereits automatisch die notwendigen Funktionen erzeugen. So lassen sich die Werte Morning und Afternoon des Datentyps Day sowohl auf Gleichheit testen als auch ausgeben. Dazu leite ich Day von der Typklasse Eq und Show ab.

data Day= Morning | Afternoon
     deriving (Eq,Show)

 

Mein Datentyp Day lässt sich direkt in Haskell's interaktiven Shell testen. Formal gesprochen heißt die interaktive Shell REPL und ist in vielen Programmiersprachen wie Python zu Hause. Dieses Akronym steht für Read Evaluate Print Loop.

 day

Typklassen in Haskell bieten aber noch viel mehr. So können zum Beispiel eigene Typklassen definiert werden.

Concepts Lite für Funktionen, Klassen und Mitglieder einer Klasse

Concepts Lite sind Teil der Templatedeklaration.

Funktionen

Das Funktions-Template sort fordert,

template<Sortable Cont>
void sort(Cont& container){...}


dass der Container sortierbar (Sortable) sein muss. Äquivalent lässt sich die Anforderung an den Template-Parameter auch explizit formulieren:

 

template<typename Cont>
  requires Sortable<Cont>()
void sort(Cont& container){...}

 

Sortable selbst muss ein konstanter Ausdruck sein, der ein Prädikat ist. Das heißt, dass er zur Übersetzungszeit ausgewertet werden kann und einen Wahrheitswert zurückgibt.

Wird nun der sort-Algorthmus mit einem Container lst verwendet, der nicht sortierbar ist, moniert das der Compiler mit einer eindeutigen Fehlermeldung.

std::list<int> lst = {1998,2014,2003,2011};
sort(lst); // ERROR: lst is no random-access container with <

 

Concepts Lite lassen sich bei allen Templates verwenden.

Klassen

So lässt sich ein Klassen-Template MyVector definieren, der nur Objekte als Elemente besitzen kann:

template<Object T>
class MyVector{};

MyVector<int> v1; // OK
MyVector<int&> v2 // ERROR: int& does not satisfy the constraint Object

 

In diesem Fall moniert der Compiler, das ein Zeiger (int&) kein Objekt ist. MyClass kann noch weiter verfeinert werden.

Mitglieder einer Klasse

template<Object T>
class MyVector{
  ...
  requires Copyable<T>()
  void push_back(const T& e);
  ...
};

 

Nun fordert die Methode push_back von MyVector, dass die Elemente kopierbar sein müssen.

Erweiterte Funktionalität

Ein Template kann mehrere Anforderungen an sein Template-Parameter stellen.

Mehrere Anforderungen

template <SequenceContainer S,EqualityComparable<value_type<S>> T>
Iterator_type<S> find(S&& seq, const T& val){...}

 

So stellt das Funktion-Template find zwei Bedingungen. Zum einen muss sein Container seine Elemente wie eine Sequenz (SequenceContainer) im Speicher ablegen, zum anderen müssen sich die Elemente des Containers auf Gleichheit (EqualityComparable<value_type<S>>) vergleichen lassen.

Aber auch das Überladen von Funktionen unterstützen Concepts Lite.

Überladen von Funktionen

template<InputIterator I>
void advance(I& iter, int n){...}

template<BidirectionalIterator I>
void advance(I& iter, int n){...}

template<RandomAccessIterator I>
void advance(I& iter, int n){...}

std::list<int> lst{1,2,3,4,5,6,7,8,9};
std::list<int>:: iterator i= lst.begin();
std::advance(i,2);   //  BidirectionalIterator

 

Das Funktions-Template advance setzt seinen Iterator iter um n Positionen weiter. Abhängig davon, ob der Iterator nur vorwärts, in beide Richtungen oder beliebig positioniert werden kann, kommen verschieden Funktions-Templates zum Einsatz. Im konkreten Fall der Liste, wird die BidirectionalIterator vom Compiler ausgewählt.

Concepts Lite unterstützen das Spezialisieren von Klassen-Templates.

Spezialisieren von Klassen-Templates

template<typename T>
class MyVector{};

template<Object T>
class MyVector{};

MyVector<int> v1; // Object T
MyVector<int&> v2 // typename T

 

So bildet der Compiler MyVector<int&> v2 auf das uneingeschränkte Template in erste  Zeile ab, MyVector<int> v1 hingegen auf das eingeschränkte Template template<Object T> class MyVector{};.

Wie geht's weiter?

Haskell kennt die Typklasse Monade. Eine bekannte Instanz ist die Maybe-Monade. Warum sage ich das?. Ganz einfach. C++17 erhält mit dem Datentyp std::optional eine Monade, die wie die Maybe-Monade eine Berechnung repräsentiert, die ein oder kein Ergebnis zurückgeben kann. Die Details zu std::optional folgen im nächsten Artikel.

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Hole dir dein E-Book. Unterstütze meinen Blog.

 

Tags: Concepts

Kommentar schreiben


Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare