C++20: Concepts – die Details
Concepts erlauben es, semantische Einschränkungen auf Template-Parametern auszudrücken. Hier nun ein Artikel mit vielen kleinen Anwendungsfällen zu Concepts.
Im letzten Artikel "C++20: Zwei Extreme und die Rettung durch Concepts [1]" bin ich auf die Motivation für Concepts eingegangen. Concepts erlauben es, semantische Einschränkungen auf Template-Parametern auszudrücken. Heute stelle ich viele kleine Anwendungsfälle zu Concepts kurz und bündig vor.
Zuerst möchte ich an die Vorteile von Concepts erinnern.
- Die Anforderungen an die Templates sind Bestandteil des Interfaces.
- Das Überladen von Templates oder die Spezialisierung von Templates ist aufgrund von Concepts möglich.
- Wir erhalten verbesserte Fehlermeldungen, denn der Compiler kann die Bedingungen an die Template-Parameter mit den Template-Argumenten vergleichen.
- Du kannst vordefinierte Concepts verwenden oder deine eigenen Concepts definieren.
- Die Verwendung von
auto
und Concepts wird vereinheitlicht. Daher lässt sich anstelle vonauto
einfach ein Concept verwenden.
- Wenn eine Funktionsdeklaration ein Concept verwendet, wird die Funktion automatisch zum Funktions-Template. Das Schreiben von Funktions-Templates wird damit so einfach wie das Schreiben von Funktionen.
![C++20: Concepts, die Details](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/7/9/8/1/1/8/TimelineCpp20Concepts-56bf1b037828d6c9.png)
In diesem Artikel werde ich mich mit den ersten drei Punkten der obigen Zusammenstellung genauer beschäftigen. Los geht es mit verschiedenen Anwendungen von Concepts:
Drei Arten
Auf drei Arten lässt sich das Concept Sortable
einsetzen. Ich stelle lediglich die Deklaration eines Funktions-Templates vor:
- Requires Clause
template<typename Cont>
requires Sortable<Cont>
void sort(Cont& container);
- Trailing Requires Clause
template<typename Cont>
void sort(Cont& container) requires Sortable<Cont>;
- Constrained Template Parameter
template<Sortable Cont>
void sort(Cont& container)
Der Algorithmus sort
fordert, dass der Container sortierbar ist. Sortable
muss ein konstanter Ausdruck und ein Prädikat sein.
Klassen
Du kannst ein Klassen-Template definieren, das nur Objekte annimmt:
template<Object T>
class MyVector{};
MyVector<int> v1; // OK
MyVector<int&> v2; // ERROR: int& does not satisfy the constraint Object
Der Compiler beschwert sich in diesem Fall, dass eine Referenz kein Objekt ist. Du fragst dich vermutlich: Was ist ein Objekt? Ein mögliche Implementierung der Typ-Traits Funktion std::is_object
gibt die Antwort:
template< class T>
struct is_object : std::integral_constant<bool,
std::is_scalar<T>::value ||
std::is_array<T>::value ||
std::is_union<T>::value ||
std::is_class<T>::value> {};
Ein Objekt ist entweder ein Skalar, ein Array, eine Union oder eine Klasse.
Member-Funktionen
template<Object T>
class MyVector{
...
void push_back(const T& e) requires Copyable<T>{}
...
};
In diesem Fall fordert die Member-Funktion push_back
, dass der Template-Parameter T
kopierbar sein muss.
Variadic Templates
// allAnyNone.cpp
#include <iostream>
#include <type_traits>
template<typename T>
concept Arithmetic = std::is_arithmetic<T>::value;
template<Arithmetic... Args>
bool all(Args... args) { return (... && args); }
template<Arithmetic... Args>
bool any(Args... args) { return (... || args); }
template<Arithmetic... Args>
bool none(Args... args) { return !(... || args); }
int main(){
std::cout << std::boolalpha << std::endl;
std::cout << "all(5, true, 5.5, false): " << all(5, true, 5.5, false) << std::endl;
std::cout << "any(5, true, 5.5, false): " << any(5, true, 5.5, false) << std::endl;
std::cout << "none(5, true, 5.5, false): " << none(5, true, 5.5, false) << std::endl;
}
Concepts können auch in Variadic Templates angewandt werden. Die Definitionen der Funktions-Temples verwenden Fold Expressions [2]. all
, any
und none
verlangen von ihrem Typ-Parameter, dass er das Concept Arithmetic unterstützt. Arithmetic
ist T
dann, wenn es eine Ganzzahl oder eine Fließkommazahl ist.
Der aktuelle Microsoft-Compiler 19.23 unterstützt als einziger teilweise die neue Syntax für Concepts:
![C++20: Concepts, die Details](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/7/9/8/1/1/8/allAnyNone-e018b8c42d66ad38.png)
Mehrere Anforderungen
Natürlich lässt sich an einen Templater-Parameter mehr als eine Anforderung stellen:
template <SequenceContainer S,
EqualityComparable<value_type<S>> T>
Iterator_type<S> find(S&& seq, const T& val){
...
}
Das Funktions-Template find
fordert:
- Sein Container
S
soll einSequenceContainer
sein. - Die Elemente
T
des ContainerS
sollenEqualityComparable
sein
Überladen
std::advance(iter, n)
setzt seinen Iterator iter n
Positionen weiter. Abhängig von dem Iterator kann die Implementierung Zeigerarithmetik anwenden oder lediglich n Schritte weiter gehen. Im ersten Fall ist die Ausführungszeit konstant; im zweiten Fall hängt die Ausführungszeit von der Schrittweite n ab. Dank Concepts kann std::advance
aufgrund der Iterator-Kategorie überladen werden:
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){...}
// usage
std::vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto vecIt = vec.begin();
std::advance(vecIt, 5); // RandomAccessIterator
std::list<int> lst{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto lstIt = lst.begin();
std::advance(lstIt, 5); // BidirectionalIterator
std::forward_list<int> forw{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto forwIt = forw.begin();
std::advance(forwIt, 5); // InputIterator
Basierend auf der Iterator-Kategorie, die die Container std::vector
, std::list
und std::forward_list
anbieten, kommt die am besten passende Implementierung von std::advance
zum Einsatz.
Spezialisierung
Concepts unterstützen natürlich auch die Template-Spezialisierung:
template<typename T>
class MyVector{};
template<Object T>
class MyVector{};
MyVector<int> v1; // Object T
MyVector<int&> v2; // typename T
-
MyVector<int&>
führt zum Aufruf des Klassen-Templates mit dem uneingeschränkten Template-Parameter. MyVector<int>
führt zum Aufruf des Klassen-Templates mit dem eingeschränkten Template-Parameter.
Wie geht't weiter?
In meinem nächsten Artikel schreibe ich über syntaktische Vereinheitlichung in C++20. Mit C++20 lässt sich an jeder Stelle ein constrained placeholder (Concept) verwenden, an der sich mit C++11 ein unconstrained placeholer (auto) einsetzen lässt. Dies ist aber noch nicht der Endpunkt der Vereinheitlichung. Die Definition von Templates wird in C++20 ein Kinderspiel. Verwende dazu einfach einen constrained oder einen unconstrained placeholder in der Deklaration einer Funktion. ( [3])
URL dieses Artikels:
https://www.heise.de/-4599997
Links in diesem Artikel:
[1] https://www.heise.de/blog/C-20-Zwei-Extreme-und-die-Rettung-dank-Concepts-4594834.html
[2] https://www.grimm-jaud.de/index.php/44-blog/funktional/190-fold-expressions
[3] mailto:rainer@grimm-jaud.de
Copyright © 2019 Heise Medien