Concepts definieren

Inhaltsverzeichnis[Anzeigen]

Ich schrieb schon ein paar Artikel über Concepts. Concepts sind Anforderungen, die Namen besitzen. In diesem Artikel definiere ich ein paar Concepts.

Du kannst ein Concept mit einem Funktions-Template oder einem Variablen-Template definieren. Variable-Template gibt es seit C++14 gibt und stellt eine Familie von Variablen dar. Falls du dein Concept mit einem Funktions-Template definierst, nennt sich dies Funktions-Concept. Im zweiten Fall Variable-Concept.

Zwei Gestalten


template
<typename T> concept bool Integral = std::is_integral<T>::value; } template<typename T> concept bool Equal(){ return requires(T a, T b) { { a == b } -> bool; { a != b } -> bool; }; }

Integral ist ein Variable-Concept und Equal ist ein Funktions-Concept. Beide geben einen Wahrheitswert zurück.

  • Der Typ-Parameter T erfüllt das Variable-Concept, falls std::is_integral<T>::value zu true evaluiert.
  • Der Typ-Parameter T erfüllt das Funktions-Concept, falls die Operatoren == und != so für T überladen sind, dass sie einen Wahrheitswert zurückgeben.

Um ehrlich zu sein, die Definition des Funktions-Konzept Equal kommt mir sehr vertraut vor. Warum? Warte ein paar Sätze. Lass mich erst das Concept anwenden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// conceptsDefintionEqual.cpp

#include <iostream>

template<typename T>
concept bool Equal(){
  return requires(T a, T b) {
    { a == b } -> bool;
    { a != b } -> bool;
  };
}

bool areEqual(Equal a, Equal b){
  return a == b;
}

/*

struct WithoutEqual{
  bool operator==(const WithoutEqual& other) = delete;
};

struct WithoutUnequal{
  bool operator!=(const WithoutUnequal& other) = delete;
};

*/

int main(){
  
  std::cout << std::boolalpha << std::endl;
  
  std::cout << "areEqual(1, 5): " << areEqual(1, 5) << std::endl;
  
  /*
  
  bool res = areEqual(WithoutEqual(),  WithoutEqual());
  
  bool res2 = areEqual(WithoutUnequal(),  WithoutUnequal());
  
  */
  
  std::cout << std::endl;
  
}

I verwende das Concept Equal in der (generischen) Funktion areEqual (Zeile 13 bis 15). Das ist nicht so aufregend. Hier kommt die Ausgabe der Funktion areEqual:

conceptsDefinitionEqual

Deutlich interessanter ist es, falls ich die Klassen WithoutEqual und WithoutUnequal verwende. Für beide Klassen habe ich den == bzw. den != Operator auf delete gesetzt. Mein Compiler beschwert sich sofort, dass beide Typen das Concept nicht erfüllen.

conceptsDefinitionError

Equal kommt mir vertraut vor. Nun sage ich, warum.

Das Concept Equal und Ord

typeclass

Dies ist ein Teil der Typhierarchie  von Haskells Typklassen. Es gibt eine Art von Vererbung, angedeutet durch die Pfeile. Im linken, oberen Eck ist die Typklasse Eq. Nun bin ich neugierig, wie Eq definiert ist.

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

template<typename T>
concept bool Equal(){
  return requires(T a, T b) {
    { a == b } -> bool;
    { a != b } -> bool;
  };
}

Lass uns genauer auf Haskells Typklasse Eq schauen.  Eq fordert von seinen Instanzen, dass

  • diese eine Gleich-  == und Ungleich- /= Operation besitzen, die Bool zurückgibt.
  • beide zwei Argumente desselben Typs verwenden: (a -> a).

Klar, die Instanzen der Typklasse sind die konkreten Typen wie Int.

Nun habe ich zwei Fragen in meinem Kopf, wenn ich auf Haskells Typhierarchie schaue. Wie ist die Typklasse Ord in Haskell definiert und können wir eine Art von Vererbung in C++ modellieren?

Wie ist die Typklasse Ord in Haskell definiert

Ord

class Eq a => Ord a where
  compare :: a -> a -> Ordering
  (<) :: a -> a -> Bool
  (<=) :: a -> a -> Bool
  (>) :: a -> a -> Bool
  (>=) :: a -> a -> Bool
  max :: a -> a -> a

Der interessanteste Punkt in der Definition der Typklasse Ord ist die erste Zeile. Eine Instanz der Typklasse Ord muss bereits eine Instanz der Typklasse Eq sein. Ordering ist eine Aufzählung, die die Werte EQ, LT und GT annehmen kann.

Wie können wir eine Art von Vererbung in C++ modellieren?

Eq -> Ord

Natürlich können wir einfach ein Concept Ord definieren, in dem wir alle Anforderungen von Eq und Ord verwenden. Aber das geht deutlich eleganter in C++:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <unordered_set>

template<typename T>
concept bool Equal(){
  return requires(T a, T b){
    { a == b } -> bool;
    { a != b } -> bool;
  };
}

template <typename T>
concept bool Ord(){
  return requires(T a, T b){
    requires Equal<T>();
    { a <= b } -> bool;
    { a < b } -> bool;
    { a > b } -> bool;
    { a >= b } -> bool;
  };
}

bool areEqual(Equal a, Equal b){
  return a == b;
}

Ord getSmaller(Ord a, Ord b){
  return (a < b) ? a : b;
}
    
int main(){
  
  std::cout << std::boolalpha << std::endl;
  
  std::cout << "areEqual(1, 5): " << areEqual(1, 5) << std::endl;
  
  std::cout << "getSmaller(1, 5): " << getSmaller(1, 5) << std::endl;
  
  std::unordered_set<int> firSet{1, 2, 3, 4, 5};
  std::unordered_set<int> secSet{5, 4, 3, 2, 1};
  
  std::cout << "areEqual(firSet, secSet): " << areEqual(firSet, secSet) << std::endl;
  
  // auto smallerSet= getSmaller(firSet, secSet);
  
  
  std::cout << std::endl;
  
}

 

Um meinen Job ein weniger einfacher zu machen, habe ich die Anforderungen compare und max in dem Concept  Ord ignoriert. Die entscheidende Zeile in der Definition des Concepts ist die Zeile requires Equal<T>(). Hier fordere ich, dass der Typ-Parameter T der Anforderung Equal genügen muss. Falls ich mehrere Anforderungen wie in der Defintion des Concept Equal verwende, wird jedes von Anfang bis Ende geprüft. Hier kommt Kurzschluss-Auswertung zum Einsatz. Das bedeutet, die erste Anforderung, die zu false evaluiert, beendet den Prozess.

Gleichheit und Ungleichheit sind für den Datentyp int und std::unordered_set definiert. Daher sollte dich die Ausgabe nicht überraschen.

conceptsDefinitionOrd

 Das ändert sich aber drastisch, falls ich die Zeile 44 verwenden, denn die Kleiner/Größer Relationen sind für std::unordered_set nicht definiert.

conceptsDefinitionOrdError

Wie geht's weiter?

Ich schreib ein paar Artikel für das Linux-Magazin und iX über C++17. Einer meiner englischen Blog Leser frage mich, ob er diese in Englisch lesen kann. Meine einfache Antwort war nein. Aber ich versprach ihn in meinen nächsten Artikel über C++17 zu schreiben.

 

 

 

 

title page small{end-texte}title 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.

Kommentar schreiben


Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare