Freigeben über


<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_rangeund output_range. Die Ausnahme ist input_range, die nicht geschrieben werden kann, sodass sie nicht über die Funktionen verfügt output_range.

Diagramm der Bereichs iteratorhierarchie. input_range und output_range sind die grundlegendsten Iteratoren. forward_range wird anschließend sowohl input_range als auch output_range optimiert. bidirectional_range optimiert forward_range. random_access_range optimiert bidirectional_range. Schließlich verfeinern contiguous_range random_access_range

Weitere Bereichskonzepte sind:

Bereichskonzept Beschreibung
rangeC++20 Gibt einen Typ an, der einen Iterator und einen Sentinel bereitstellt.
borrowed_rangeC++20 Gibt an, dass die Lebensdauer der Iteratoren des Bereichs nicht an die Lebensdauer des Bereichs gebunden ist.
common_rangeC++20 Gibt an, dass der Typ des Iterators des Bereichs und der Typ des Sentinels des Bereichs identisch sind.
Simple_ViewC++20 Kein offizielles Konzept, das als Teil der Standardbibliothek definiert ist, sondern als Hilfskonzept für einige Schnittstellen verwendet wird.
sized_rangeC++20 Gibt einen Bereich an, der die Anzahl der Elemente effizient bereitstellen kann.
viewC++20 Gibt einen Typ an, der eine effiziente (konstante Zeit) Verschiebungskonstruktion, Zuordnung und Zerstörung aufweist.
viewable_rangeC++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_rangehandelt, 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 eine input_iterator. Das Aufrufen begin() mehrerer Aufrufe für ein nicht definiertes Verhalten führt zu einem input_range nicht definierten Verhalten.
  • Sie können eine input_iterator wiederholte Ableitung ausführen, die denselben Wert jedes Mal zurückgibt. Ein input_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 Rschreiben 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_rangeund 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() und std::ranges::end()
  • ranges::begin() und ranges::end() führen sie in amortisierten Konstantenzeit aus, und ändern Sie nicht die range. 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 Ansicht
  • const V ist ein Bereich
  • Sowohl als const V auch v 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 inputFunktion , 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_baseabgeleitet, 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.

Siehe auch

<ranges>
Bereichsadapter
Anzeigen von Klassen