C++20: Die Concepts SemiRegular und Regular definieren
Ein konkreter C++-Datentyp sollte sich wie ein int verhalten. Er sollte das Concept Regular unterstützen. In diesem Artikel werden die Concepts SemiRegular und Regular definiert.
Ein konkreter Datentyp, der sich intuitiv im C++-Ökosystem verwenden lässt, sollte sich wie ein int
verhalten. Formaler ausgedrückt, sollte der konkrete Datentyp das Concept Regular
unterstützen. In diesem Artikel werde ich die Concepts SemiRegular
und Regular
definieren.
Regular
und SemiRegular
sind wichtige Ideen in C++. Sorry, ich sollte Concepts sagen. Zum Beispiel lautet die Regel T.46 der C++ Core Guidelines: T.46: Require template arguments to be at least Regular or [1]SemiRegular [2]
. Jetzt muss ich nur noch eine Frage beantworten. Was sind Regular
- und SemiRegula
r
-Datentypen? Bevor ich in die Details eintauche, ist das die informelle Antwort:
Ein Regular
-Datentyp "behaves like an int
". Er kann kopiert werden und das Ergebnis des Kopierens ist unabhängig vom Original. Beide haben nach dem Kopieren den gleichen Wert.
Jetzt wird es formaler. Ein Regular-
Datentyp ist ein SemiRegula
r
-Datentyp. Konsequenterweise starte ich mit einem SemiRegular
-Datentyp.
SemiRegular
Ein SemiRegular
-Datentyp muss die Sechserregel [3] umsetzen und zusätzlich swap
unterstützen.
- Default-Konstruktor:
X()
- Copy-Konstruktor:
X(const X&)
- Copy-Zuweisungsoperator:
operator=(const X&)
- Move-Konstruktor:
X(X&&)
- Move-Zuweisungsoperator:
operator=(X&&)
- Destruktor:
~X()
- swap:
swap(X&, Y&)
Das war einfach. Dank der Typ-Traits-Bibliothek ist die Definition des entsprechendes Concepts reine Fingerarbeit. Erst werde ich das Typ-Trait isSemiRegular
definieren und mit diesem das Concept SemiRegular
umsetzen:
template<typename T>
struct isSemiRegular: std::integral_constant<bool,
std::is_default_constructible<T>::value &&
std::is_copy_constructible<T>::value &&
std::is_copy_assignable<T>::value &&
std::is_move_constructible<T>::value &&
std::is_move_assignable<T>::value &&
std::is_destructible<T>::value &&
std::is_swappable<T>::value >{};
template<typename T>
concept SemiRegular = isSemiRegular<T>::value;
Jetzt geht es weiter mit dem Concept Regular
.
Regular
Ein kleiner Schritt fehlt noch, und ich kann das Concept Regular
definieren. Zusätzlich zum Concept SemiRegular
verlangt das Concept Regular
von seinen Datentypen, dass sie auch Gleichheit unterstützen. Ich habe bereits in meinem letzten Artikel [4] das Concept Equal
implementiert:
template<typename T>
concept Equal =
requires(T a, T b) {
{ a == b } -> std::convertible_to<bool>;
{ a != b } -> std::convertible_to<bool>;
};
Nun muss ich nur noch das Concept Equal
für das Concept Regular
wiederverwenden:
template<typename T>
concept Regular = Equal<T> &&
SemiRegular<T>;
Jetzt bin ich neugierig. Wie sind die beiden Concepts SemiRegular
und Regular
im C++20-Standard definiert?
regular und semiregular in C++20
template<class T>
concept movable = is_object_v<T> && move_constructible<T> &&
assignable_from<T&, T> && swappable<T>;
template<class T>
concept copyable = copy_constructible<T> && movable<T>
&& assignable_from<T&, const T&>;
template<class T>
concept semiregular = copyable<T> && default_constructible<T>;
template<class T>
concept regular = semiregular<T> && equality_comparable<T>;
Nebenbei gesagt, es gibt außer didaktischen Gründen keinen Grund, die Concepts Regular
und SemiRegular
selbst zu definieren.
Interessanterweise ist das Concept regular
meinem Concept Regular
sehr ähnlich. Diese Beobachtung gilt nicht für das Concept semiregular
, das aus elementaren Concepts wie copyable
und moveable
komponiert ist. Das Concept movable
basiert auf der Funktion is_object
[5] aus der Typ-Traits-Bibliothek. Auf der referenzierten Seite findet sich auch eine mögliche Implementierung des Typ-Traits is_object
.
Jetzt fehlt noch der letzte Schritt in meinem Artikel: die Concepts anwenden.
Anwendung der Concepts regular und regular
Um es einfach zu machen, prüfen die Funktions-Template behavesLikeAnInt
und behavesLikeAnInt2
, ob die Argumente sich wie int
's verhalten. Das heißt, dass ich mein Concept Regular
und das Concept regula
r
des C++20-Standards verwende, um die Typanforderung zu prüfen:
// regularSemiRegular.cpp
#include <concepts>
#include <vector>
#include <utility>
template<typename T>
struct isSemiRegular: std::integral_constant<bool,
std::is_default_constructible<T>::value &&
std::is_copy_constructible<T>::value &&
std::is_copy_assignable<T>::value &&
std::is_move_constructible<T>::value &&
std::is_move_assignable<T>::value &&
std::is_destructible<T>::value &&
std::is_swappable<T>::value >{};
template<typename T>
concept SemiRegular = isSemiRegular<T>::value;
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> // (1)
concept Regular = Equal<T> &&
SemiRegular<T>;
template <Regular T> // (2)
void behavesLikeAnInt(T) {
// ...
}
template <std::regular T> // (3)
void behavesLikeAnInt2(T) {
// ...
}
struct EqualityComparable { }; // (4)
bool operator == (EqualityComparable const&, EqualityComparable const&) {
return true;
}
struct NotEqualityComparable { }; // (5)
int main() {
int myInt{};
behavesLikeAnInt(myInt);
behavesLikeAnInt2(myInt);
std::vector<int> myVec{};
behavesLikeAnInt(myVec);
behavesLikeAnInt2(myVec);
EqualityComparable equComp;
behavesLikeAnInt(equComp);
behavesLikeAnInt2(equComp);
NotEqualityComparable notEquComp;
behavesLikeAnInt(notEquComp); // (6)
behavesLikeAnInt2(notEquComp); // (7)
}
Ich habe einfach alle Bausteinchen der vorherigen Codebeispiel zusammengestellt und damit das Concept Regular
(Zeile 1) definiert. Die Funktionen behavesLikeAnInt
(Zeile 2) und behavesLikeAnIn
t2
(Zeile 3) verwenden die Concepts. Wie es der Name verspricht, unterstützt der Datentyp EqualityCompareable
(Zeile 4) Gleichheit. Dies gilt aber nicht für den Datentyp NotEqualityComarable
(Zeile 5). Am Interessantesten ist die Anwendung des Datentyps NotEqualityComparable
in den Zeilen 6 und 7.
GCC
Wenn du das Programm in Aktion sehen willst, verwende den Link auf den Compiler Explorer: https://godbolt.org/z/XAJ2w3 [6]. Die Fehlermeldung im Compiler Explorer mit dem GCC ist sehr genau, aber ein wenig unübersichtlich. Dies ist wohl der Tatsache geschuldet, dass beide Concepts fehlgeschlagen sind, Concepts sich noch in einem frühen Implementierungsstadium befinden und Online-Werkzeuge nicht so komfortabel wie eine Konsole sind.
- Das Concept
Regular
Dies sind die entscheidenden Zeilen, die mein Concept Regular
(Zeile 6) mithilfe des Compiler Explorer ausgibt:
- Das Concept
regular
Das C++20-Concept regular
besitzt eine ausgefeiltere Implementierung. Daher erzeugt sich auch eine ausgefeiltere Fehlermeldung:
MSVC
Die Fehlermeldung des Windows-Compilers ist mir zu unspezifisch:
Wie geht's weiter?
Mit diesem Artikel habe ich meine Miniserie zu Concepts abgeschlossen. Natürlich bin ich jetzt an deiner Meinung interessiert: Sind Concepts eine Evolution oder eine Revolution in C++? Schicke mir doch deine Meinung oder schreibe einen Kommentar, bis einschließlich Donnerstag, den 6. Februar. Gerne möchte ich das Stimmungsbild in meinem nächsten Artikel zusammenfassen. Falls ich dich namentlich nennen soll, schreibe mir dies explizit und nenne deinen Namen. ( [7])
URL dieses Artikels:
https://www.heise.de/-4651337
Links in diesem Artikel:
[1] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-regular
[2] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-regular
[3] https://heise.de/-3813435
[4] https://heise.de/-4641098
[5] https://en.cppreference.com/w/cpp/types/is_object
[6] https://godbolt.org/z/XAJ2w3
[7] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien