zurück zum Artikel

C++20: Die Concepts Equal und Ordering definieren

Rainer Grimm

Nach der Definition des Concepts Equal geht unser Blogger Rainer Grimm einen Schritt weiter und verwendet das Concept Equal, um das Concept Ordering zu definieren.

In meinem letzten Artikel [1] habe ich das Concept Equal definiert. Heute gehe ich einen Schritt weiter und verwende das Concept Equal, um das Concept Ordering zu definieren.

Zur Erinnerung: Das folgende Beispiel zeigt das Concept Equal und die Funktion areEqual aus dem letzten Artikel:

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


bool areEqual(Equal auto fir, Equal auto sec) {
return fir == sec;
}
C++20: Die Concepts Equal und Ordering definieren

Ich wendete das Concept Equal in meinem letzten Artikel auf falsche Art an. Es fordert von a und b, dass beide denselben Datentyp besitzen müssen. Die Funktion areEqual erlaubt es aber, dass fir und sec verschiedene Datentypen sein können. Beide müssen natürlich das Concept Equal unterstützen. Die Verwendung eines Constrained Template anstelle der Placeholder-Syntax löst das Problem:

template <Equal T>
bool areEqual(T fir, T sec) {
fir == sec;
}

fir und sec müssen jetzt denselben Datentyp besitzen.

(Vielen Dank an Corentin Jabot, der mich auf diese Asymmetrie aufmerksam gemacht hat.)

Zusätzlich sollte das Concept Equal nicht prüfen, ob der Gleichheits- und Ungleichheitsoperator bool zurückgibt. Das Concept sollte prüfen, ob sich der Rückgabewert zu bool implizit oder explizit konvertieren lässt. Hier ist die verbesserte Definition des Concepts:

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

std::convertible_to ist selbst ein Concept des C++20-Standards und benötigt daher die Headerdatei <concepts>:

template <class From, class To>
concept convertible_to =
std::is_convertible_v<From, To> &&
requires(From (&f)()) {
static_cast<To>(f());
};

Der C++20-Standard definiert bereits zwei Concepts für Gleichheit:

Ich habe meinen letzten Artikel damit beendet, dass ich einen Teil von Haskells Typklassen-Hierarchie vorgestellt habe:

C++20: Die Concepts Equal und Ordering definieren

Die Klassenhierarchie zeigt, dass die Typklasse Ord eine Verfeinerung der Typklasse Eq ist. Dies lässt sich elegant in Haskell ausdrücken:

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

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

Hier ist meine Herausforderung: Lässt sich diese Beziehung ähnlich elegant in C++ mit Concepts ausdrücken? Der Einfachheit halber ignoriere ich die Funktionen compare und max aus Haskells Typklasse Ord.

Dank der requires-expressions besitzt die Definition des Concepts Ordering eine große Ähnlichkeit mit der Definition der Typklasse Ord.

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

Das folgende Beispiel zeigt das Concept Ordering in Aktion:

// conceptsDefinitionOrdering.cpp

#include <concepts>
#include <iostream>
#include <unordered_set>

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


template <typename T>
concept Ordering =
Equal<T> &&
requires(T a, T b) {
{ a <= b } -> std::convertible_to<bool>;
{ a < b } -> std::convertible_to<bool>;
{ a > b } -> std::convertible_to<bool>;
{ a >= b } -> std::convertible_to<bool>;
};

template <Equal T>
bool areEqual(T a, T b) {
return a == b;
}

template <Ordering T>
T getSmaller(T a, T 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;

}

Die Funktion getSmaller fordert, dass beide Argumente a und b das Concept Ordering unterstützen. Darüber hinaus müssen sie denselben Datentyp besitzen. Diese Anforderung gilt natürlich für die Zahlen 1 und 5.

C++20: Die Concepts Equal und Ordering definieren

std::unordered_set setzt das Concept Ordering nicht um. Der aktuelle Microsoft-Compiler ist sehr eindeutig, wenn ich die Zeile auto smaller = getSmaller(firSet, secSet) mit dem Flag /std:c++latest übersetze.

C++20: Die Concepts Equal und Ordering definieren

Ich möchte hervorheben, dass die Fehlermeldung das Problem sehr gut auf den Punkt bringt: the associated constraints are not satisfied.

Natürlich besitzt C++20 bereits das Concept Ordering:

Eventuell verwirrt dich ein wenig der Ausdruck three-way. Mit C++20 erhalten wir den Drei-Wege-Vergleichsoperator; auch bekannt unter dem Namen Spaceship Operator: <=>. Hier gibt es den ersten Überblick: C++20: Überblick zur Kernsprache [2]. Den Operator werde ich in einem zukünftigen Artikel genauer vorstellen.

Ich lerne Neues immer am besten, wenn ich es ausprobiere. Eventuell hast du keinen aktuellen Microsoft-Compiler. In diesem Fall bietet es sich an, den aktuellen GCC (trunk) des Compiler Explorer zu verwenden. GCC unterstützt die C++20-Syntax für Concepts. Hier ist der direkte Link zu dem Programm conceptsDefinitionOrdering.cpp für weitere Experimente: https://godbolt.org/z/uyVFX8 [3].

Wenn du einen konkreten Datentyp definieren willst, der sich einfach im C++-Ökosystem verwenden lässt, sollte er sich "wie ein int verhalten". Solch ein konkreter Typ lässt sich kopieren und das Ergebnis der Copy-Operation ist unabhängig vom ursprünglichen Wert. Beide Objekte besitzen natürlich nach der Copy-Operation den gleichen Wert. Formal gesprochen, sollte ein konkreter Datentyp ein regular type sein. In meinem nächsten Artikel werde ich die Concepts Regular und SemiRegular definieren.

Ich freue mich darauf, weitere C++-Schulungen halten zu dürfen.

Die Details zu meinen C++- und Python-Schulungen gibt es auf www.ModernesCpp.de [6]. ( [7])


URL dieses Artikels:
https://www.heise.de/-4641098

Links in diesem Artikel:
[1] https://heise.de/-4641100
[2] https://www.heise.de/blog/C-Die-Kernsprache-4574153.html
[3] https://godbolt.org/z/uyVFX8
[4] https://www.modernescpp.de/index.php/c/2-c/22-embedded-programmierung-mit-modernem-c20190511084954
[5] https://www.modernescpp.de/index.php/c/2-c/23-c-11-und-c-1420190917134222
[6] https://www.modernescpp.de/
[7] mailto:rainer@grimm-jaud.de