<ranges>
-Konzepte
Konzepte sind ein C++20-Sprachfeature, das Vorlagenparameter zur Kompilierungszeit einschränkt. Sie helfen dabei, falsche Vorlageninstanziierung zu verhindern, Vorlagenargumentanforderungen in lesbarer Form anzugeben und prägnantere Compilerfehler bereitzustellen.
Betrachten Sie das folgende Beispiel, das ein Konzept definiert, um zu verhindern, dass eine Vorlage mit einem Typ instanziiert wird, der keine Division unterstützt:
// requires /std:c++20 or later
#include <iostream>
// Definition of dividable concept which requires
// that arguments a & b of type T support division
template <typename T>
concept dividable = requires (T a, T b)
{
a / b;
};
// Apply the concept to a template.
// The template will only be instantiated if argument T supports division.
// This prevents the template from being instantiated with types that don't support division.
// This could have been applied to the parameter of a template function, but because
// most of the concepts in the <ranges> library are applied to classes, this form is demonstrated.
template <class T> requires dividable<T>
class DivideEmUp
{
public:
T Divide(T x, T y)
{
return x / y;
}
};
int main()
{
DivideEmUp<int> dividerOfInts;
std::cout << dividerOfInts.Divide(6, 3); // outputs 2
// The following line will not compile because the template can't be instantiated
// with char* because char* can be divided
DivideEmUp<char*> dividerOfCharPtrs; // compiler error: cannot deduce template arguments
}
Wenn Sie den Compilerwechsel /diagnostics:caret
an Visual Studio 2022, Version 17.4 Preview 4 oder höher, übergeben, verweist das auf "false" ausgewertete Fehler dividable<char*>
direkt auf die fehlgeschlagene Ausdrucksanforderung (a / b)
.
Bereichskonzepte werden im std::ranges
Namespace definiert und in der <ranges>
Headerdatei deklariert. Sie werden in den Deklarationen von Bereichsadaptern, Ansichten usw. verwendet.
Es gibt sechs Kategorien von Bereichen. Sie beziehen sich auf die Kategorien von Iteratoren, die in <iterator>
Konzepten aufgeführt sind. Um die Funktion zu erhöhen, sind die Kategorien:
Bereichskonzept | Beschreibung |
---|---|
output_range input_range |
Gibt einen Bereich an, in den Sie schreiben können. Gibt einen Bereich an, den Sie einmal lesen können. |
forward_range |
Gibt einen Bereich an, den Sie mehrmals lesen (und möglicherweise schreiben) können. |
bidirectional_range |
Gibt einen Bereich an, den Sie sowohl vorwärts als auch rückwärts lesen und schreiben können. |
random_access_range |
Gibt einen Bereich an, den Sie nach Index lesen und schreiben können. |
contiguous_range |
Gibt einen Bereich an, dessen Elemente im Arbeitsspeicher sequenziell sind, die gleiche Größe aufweisen und mithilfe von Zeigerarithmetik darauf zugegriffen werden kann. |
In der obigen Tabelle werden Konzepte in der Reihenfolge der zunehmenden Funktion aufgeführt. Ein Bereich, der die Anforderungen eines Konzepts erfüllt, erfüllt im Allgemeinen die Anforderungen der Konzepte in den Zeilen, die ihm vorangehen. Beispielsweise verfügt ein Objekt random_access_range
über die Funktion eines bidirectional_range
, forward_range
, , input_range
und output_range
. Die Ausnahme ist input_range
, die nicht geschrieben werden kann, sodass sie nicht über die Funktionen verfügt output_range
.
Weitere Bereichskonzepte sind:
Bereichskonzept | Beschreibung |
---|---|
range C++20 |
Gibt einen Typ an, der einen Iterator und einen Sentinel bereitstellt. |
borrowed_range C++20 |
Gibt an, dass die Lebensdauer der Iteratoren des Bereichs nicht an die Lebensdauer des Bereichs gebunden ist. |
common_range C++20 |
Gibt an, dass der Typ des Iterators des Bereichs und der Typ des Sentinels des Bereichs identisch sind. |
Simple_View C++20 |
Kein offizielles Konzept, das als Teil der Standardbibliothek definiert ist, sondern als Hilfskonzept für einige Schnittstellen verwendet wird. |
sized_range C++20 |
Gibt einen Bereich an, der die Anzahl der Elemente effizient bereitstellen kann. |
view C++20 |
Gibt einen Typ an, der eine effiziente (konstante Zeit) Verschiebungskonstruktion, Zuordnung und Zerstörung aufweist. |
viewable_range C++20 |
Gibt einen Typ an, der entweder eine Ansicht ist oder in einen konvertiert werden kann. |
bidirectional_range
A bidirectional_range
unterstützt das Lesen und Schreiben des Bereichs vorwärts und rückwärts.
template<class T>
concept bidirectional_range =
forward_range<T> && bidirectional_iterator<iterator_t<T>>;
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine bidirectional_range
.
Hinweise
Diese Art von Bereich unterstützt bidirectional_iterator
oder höher.
A bidirectional_iterator
verfügt über die Funktionen eines forward_iterator
, kann aber auch rückwärts durchlaufen.
Einige Beispiele für eine bidirectional_range
sind std::set
, std::vector
, und std::list
.
borrowed_range
Ein Typmodell borrowed_range
, wenn die Gültigkeit von Iteratoren, die Sie aus dem Objekt abrufen, die Lebensdauer des Objekts überdauern kann. Das heißt, die Iteratoren für einen Bereich können auch dann verwendet werden, wenn der Bereich nicht mehr vorhanden ist.
template<class T>
concept borrowed_range =
range<T> &&
(is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine borrowed_range
.
Hinweise
Die Lebensdauer eines Wertebereichs kann nach einem Funktionsaufruf enden, unabhängig davon, ob die Bereichsmodelle vorliegen borrowed_range
oder nicht. Wenn es sich um eins borrowed_range
handelt, können Sie die Iteratoren möglicherweise weiterhin mit einem gut definierten Verhalten verwenden, unabhängig davon, wann die Lebensdauer des Bereichs endet.
Fälle, in denen dies nicht zutrifft, z. B. für Container wie vector
oder list
weil die Lebensdauer des Containers endet, beziehen sich die Iteratoren auf Elemente, die zerstört wurden.
Sie können weiterhin die Iteratoren für ein borrowed_range
, z. B. für ein view
Ähnliches iota_view<int>{0, 42}
verwenden, dessen Iteratoren über Sätze von Werten liegen, die nicht zerstört werden müssen, weil sie bei Bedarf generiert werden.
common_range
Der Typ des Iterators für ein common_range
Element entspricht dem Typ des Sentinels. Das heißt, begin()
und end()
geben Sie denselben Typ zurück.
template<class T>
concept common_range =
ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine common_range
.
Hinweise
Das Abrufen des Typs von std::ranges::begin()
und std::ranges::end()
ist wichtig für Algorithmen, die den Abstand zwischen zwei Iteratoren berechnen, und für Algorithmen, die Bereiche akzeptieren, die von Iteratorpaaren angegeben werden.
Die Standardcontainer (z. B vector
. ) erfüllen die Anforderungen von common_range
.
contiguous_range
Die Elemente eines Elements contiguous_range
werden sequenziell im Speicher gespeichert und können mithilfe von Zeigerarithmetik aufgerufen werden. Ein Array ist z. B. ein contiguous_range
.
template<class T>
concept contiguous_range =
random_access_range<T> && contiguous_iterator<iterator_t<T>> &&
requires(T& t) {{ ranges::data(t) } -> same_as<add_pointer_t<range_reference_t<T>>>;};
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine contiguous_range
.
Hinweise
Der Zugriff auf eine contiguous_range
Zeigerarithmetik ist möglich, da die Elemente sequenziell im Speicher angeordnet sind und die gleiche Größe aufweisen. Diese Art von Bereich unterstützt continguous_iterator
, was die flexibelsten der Iteratoren ist.
Einige Beispiele für eine contiguous_range
sind std::array
, std::vector
, und std::string
.
Beispiel: contiguous_range
Das folgende Beispiel zeigt die Verwendung von Zeigerarithmetik für den Zugriff auf eine contiguous_range
:
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
// Show that vector is a contiguous_range
std::vector<int> v = {0,1,2,3,4,5};
std::cout << std::boolalpha << std::ranges::contiguous_range<decltype(v)> << '\n'; // outputs true
// Show that pointer arithmetic can be used to access the elements of a contiguous_range
auto ptr = v.data();
ptr += 2;
std::cout << *ptr << '\n'; // outputs 2
}
true
2
forward_range
Ein forward_range
unterstützt das Lesen (und möglicherweise Schreiben) des Bereichs mehrmals.
template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine forward_range
.
Hinweise
Diese Art von Bereich unterstützt forward_iterator
oder höher. Eine forward_iterator
kann mehrere Male über einen Bereich durchlaufen werden.
input_range
Ein input_range
Bereich, der einmal gelesen werden kann.
template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine input_range
.
Hinweise
Wenn ein Typ die Anforderungen von input_range
:
- Die
ranges::begin()
Funktion gibt eineinput_iterator
. Das Aufrufenbegin()
mehrerer Aufrufe für ein nicht definiertes Verhalten führt zu eineminput_range
nicht definierten Verhalten. - Sie können eine
input_iterator
wiederholte Ableitung ausführen, die denselben Wert jedes Mal zurückgibt. Eininput_range
Mehrfachdurchlauf ist nicht vorhanden. Durch das Erhöhen eines Iterators werden alle Kopien ungültig. - Es kann mit
ranges::for_each
. - Sie unterstützt
input_iterator
oder höher.
output_range
Ein output_range
Bereich, in den Sie schreiben können.
template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;
Parameter
R
Der Typ des Bereichs.
T
Der Typ der Daten, die in den Bereich geschrieben werden sollen.
Hinweise
Die Bedeutung output_iterator<iterator_t<R>, T>
ist, dass der Typ einen Iterator bereitstellt, der Werte vom Typ T
in einen Typbereich R
schreiben kann. Mit anderen Worten, sie unterstützt output_iterator
oder größer.
random_access_range
Ein random_access_range
Kann einen Bereich nach Index lesen oder schreiben.
template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine sized_range
.
Hinweise
Diese Art von Bereich unterstützt random_access_iterator
oder höher. A random_access_range
verfügt über die Funktionen eines input_range
, output_range
, , forward_range
und bidirectional_range
. A random_access_range
ist sortierbar.
Einige Beispiele für eine random_access_range
sind std::vector
, std::array
, und std::deque
.
range
Definiert die Anforderungen, die ein Typ erfüllen muss, um ein range
. A range
stellt einen Iterator und einen Sentinel bereit, damit Sie seine Elemente durchlaufen können.
template<class T>
concept range = requires(T& rg)
{
ranges::begin(rg);
ranges::end(rg);
};
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine range
.
Hinweise
Die Anforderungen einer range
sind:
- Sie kann mithilfe
std::ranges::begin()
undstd::ranges::end()
ranges::begin()
undranges::end()
führen sie in amortisierten Konstantenzeit aus, und ändern Sie nicht dierange
. Amortisierte Konstante Zeit bedeutet nicht O(1), sondern dass die durchschnittliche Kosten für eine Reihe von Anrufen, auch im schlimmsten Fall, O(n) anstelle von O(n^2) oder schlimmer ist.[ranges::begin(), ranges::end())
gibt einen gültigen Bereich an.
Simple_View
A Simple_View
ist ein Nur-Exposition-Konzept, das auf einigen ranges
Schnittstellen verwendet wird. Sie ist nicht in der Bibliothek definiert. Es wird nur in der Spezifikation verwendet, um das Verhalten einiger Bereichsadapter zu beschreiben.
template<class V>
concept Simple_View = // exposition only
ranges::view<V> && ranges::range<const V> &&
std::same_as<std::ranges::iterator_t<V>, std::ranges::iterator_t<const V>> &&
std::same_as<std::ranges::sentinel_t<V>, std::ranges::sentinel_t<const V>>;
Parameter
V
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine Simple_View
.
Hinweise
Eine Ansicht ist eineSimple_View
, V
wenn alle folgenden Bedingungen erfüllt sind:
V
ist eine Ansichtconst V
ist ein Bereich- Sowohl als
const V
auchv
die gleichen Iterator- und Sentineltypen.
sized_range
A sized_range
stellt die Anzahl der Elemente im Bereich in amortisierter Konstantenzeit bereit.
template<class T>
concept sized_range = range<T> &&
requires(T& t) { ranges::size(t); };
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine sized_range
.
Hinweise
Die Anforderungen einer sized_range
Anforderung sind, dass sie folgendes aufruft ranges::size
:
- Ändert den Bereich nicht.
- Gibt die Anzahl der Elemente in amortisierter Konstantenzeit zurück. Amortisierte Konstante Zeit bedeutet nicht O(1), sondern dass die durchschnittliche Kosten für eine Reihe von Anrufen, auch im schlimmsten Fall, O(n) anstelle von O(n^2) oder schlimmer ist.
Einige Beispiele für eine sized_range
sind std::list
und std::vector
.
Beispiel: sized_range
Das folgende Beispiel zeigt, dass ein vector
von int
:sized_range
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
std::cout << std::boolalpha << std::ranges::sized_range<std::vector<int>> << '\n'; // outputs "true"
}
view
Eine view
hat konstanten Zeitverschiebungs-, Zuordnungs- und Vernichtungsvorgängen – unabhängig von der Anzahl der Elemente, über die sie verfügt. Ansichten müssen nicht konstruktierbar oder kopierbar sein, aber wenn ja, müssen diese Vorgänge auch in konstanter Zeit ausgeführt werden.
Aufgrund der konstanten Zeitanforderung können Sie Ansichten effizient verfassen. Wenn beispielsweise ein Vektor der int
aufgerufenen input
Funktion , eine Funktion, die bestimmt, ob eine Zahl durch drei divisierbar ist, und eine Funktion, die eine Zahl quadratiert, erzeugt die Anweisung auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square);
effizient eine Ansicht, die die Quadrate der Zahlen in der Eingabe enthält, die durch drei divisierbar sind. Das Verbinden von Ansichten mit |
diesen Ansichten wird als Verfassen der Ansichten bezeichnet. Wenn ein Typ dem view
Konzept entspricht, kann er effizient zusammengesetzt werden.
template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine Ansicht handelt.
Hinweise
Die wesentliche Anforderung, die eine Ansicht verfassen kann, ist, dass es billig ist, zu verschieben/zu kopieren. Dies liegt daran, dass die Ansicht verschoben/kopiert wird, wenn sie mit einer anderen Ansicht zusammengesetzt ist. Es muss ein beweglicher Bereich sein.
ranges::enable_view<T>
ist eine Eigenschaft, die verwendet wird, um die Konformität mit den semantischen Anforderungen des view
Konzepts zu behaupten. Ein Typ kann sich wie folgt anmelden:
- öffentlich und eindeutig von einer Spezialisierung ableiten
ranges::view_interface
- öffentlich und eindeutig von der leeren Klasse
ranges::view_base
abgeleitet, oder - spezialisiert auf
ranges::enable_view<T>
true
Option 1 wird bevorzugt, da view_interface
auch eine Standardimplementierung bereitgestellt wird, die einige Codebausteine speichert, die Sie schreiben müssen.
Andernfalls ist Option 2 etwas einfacher als Option 3.
Der Vorteil von Option 3 besteht darin, dass es möglich ist, die Definition des Typs zu ändern.
viewable_range
A viewable_range
ist ein Typ, der entweder eine Ansicht ist oder in eine konvertiert werden kann.
template<class T>
concept viewable_range =
range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);
Parameter
T
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine Ansicht handelt oder in eine konvertiert werden kann.
Hinweise
Wird std::ranges::views::all()
verwendet, um einen Bereich in eine Ansicht zu konvertieren.