Was ist der Zweck von C ++ 20 std :: common_reference?

Antworten:

46

common_reference kam aus meinen Bemühungen heraus, eine Konzeptualisierung der STL-Iteratoren zu entwickeln, die Proxy-Iteratoren unterstützt.

In der STL haben Iteratoren zwei Arten von besonderem Interesse: referenceund value_type. Ersteres ist der Rückgabetyp des Iterators operator*und der Typ value_type(nicht konstant, nicht referenziert) der Elemente der Sequenz.

Generische Algorithmen müssen häufig Folgendes tun:

value_type tmp = *it;

... so dass wir wissen , dass es müssen einige Beziehung zwischen diesen beiden Typen. Für Nicht-Proxy-Iteratoren ist die Beziehung einfach: Sie referenceist immer value_typeoptional optional und referenzqualifiziert. Frühe Versuche, das InputIteratorKonzept zu definieren, erforderten, dass der Ausdruck *itkonvertierbar war const value_type &, und für die interessantesten Iteratoren ist dies ausreichend.

Ich wollte, dass Iteratoren in C ++ 20 leistungsfähiger sind. Betrachten Sie beispielsweise die Anforderungen von a zip_iterator, die zwei Sequenzen im Sperrschritt durchlaufen. Wenn Sie a dereferenzieren zip_iterator, erhalten Sie einen temporären pairTyp der beiden Iteratoren reference. Also, zip‚ing a vector<int>und vector<double>hätte diesen im Zusammenhang Typen:

zipIterator reference: pair<int &, double &>
zipIterator value_type:pair<int, double>

Wie Sie sehen können, sind diese beiden Typen nicht einfach durch Hinzufügen einer Lebenslauf- und Referenzqualifikation auf höchster Ebene miteinander verbunden. Und doch fühlt es sich falsch an, die beiden Typen willkürlich unterschiedlich sein zu lassen. Hier besteht eindeutig eine Beziehung. Aber wie ist die Beziehung und was können generische Algorithmen, die mit Iteratoren arbeiten, sicher über die beiden Typen annehmen?

Die Antwort in C ++ 20 lautet, dass für jeden gültigen Iteratortyp, ob Proxy oder nicht, die Typen reference &&und value_type &eine gemeinsame Referenz verwendet werden . Mit anderen Worten, für einige Iteratoren itgibt es einen Typ CR, der Folgendes gut formuliert :

void foo(CR) // CR is the common reference for iterator I
{}

void algo( I it, iter_value_t<I> val )
{
  foo(val); // OK, lvalue to value_type convertible to CR
  foo(*it); // OK, reference convertible to CR
}

CRist die gemeinsame Referenz. Alle Algorithmen können sich auf die Tatsache verlassen, dass dieser Typ existiert, und können std::common_referencezur Berechnung verwendet werden.

Das ist also die Rolle, common_referencedie in der STL in C ++ 20 spielt. Im Allgemeinen können Sie diese ignorieren, es sei denn, Sie schreiben generische Algorithmen oder Proxy-Iteratoren. Es ist unter der Decke vorhanden, um sicherzustellen, dass Ihre Iteratoren ihren vertraglichen Verpflichtungen nachkommen.


EDIT: Das OP hat auch nach einem Beispiel gefragt. Dies ist ein wenig erfunden, aber stellen Sie sich vor, es ist C ++ 20, und Sie erhalten einen Typbereich mit wahlfreiem Zugriff, rvon Rdem Sie nichts wissen und den Sie für sortden Bereich benötigen.

Stellen Sie sich weiter vor, dass Sie aus irgendeinem Grund eine monomorphe Vergleichsfunktion verwenden möchten, wie z std::less<T>. (Vielleicht haben Sie den Bereich getippt, und Sie müssen auch die Vergleichsfunktion tippen und durch ein virtual? Wieder eine Strecke führen.) Was sollte drin Tsein std::less<T>? Dafür würden Sie verwenden common_reference, oder den Helfer, iter_common_reference_tder in Bezug darauf implementiert ist.

using CR = std::iter_common_reference_t<std::ranges::iterator_t<R>>;
std::ranges::sort(r, std::less<CR>{});

Dies funktioniert garantiert auch dann, wenn der Bereich rüber Proxy-Iteratoren verfügt.

Eric Niebler
quelle
2
Vielleicht bin ich dicht, aber können Sie klarstellen, was die allgemeine Referenz im Zip-Pair-Beispiel ist?
Happydave
4
Idealerweise pair<T&,U&>und pair<T,U>&hätte eine gemeinsame Referenz, und es wäre einfach pair<T&,U&>. Es std::pairgibt jedoch keine Konvertierung von pair<T,U>&nach pair<T&,U&>, obwohl eine solche Konvertierung im Prinzip sinnvoll ist. (Dies ist übrigens der Grund, warum wir zipin C ++ 20 keine Ansicht haben .)
Eric Niebler
4
@EricNiebler: "Aus diesem Grund haben wir in C ++ 20 übrigens keine Zip-Ansicht. " Gibt es einen Grund, warum ein Zip-Iterator pairanstelle eines Typs verwendet werden müsste , der speziell für diesen Zweck entwickelt werden könnte mit geeigneten impliziten Konvertierungen nach Bedarf?
Nicol Bolas
5
@Nicol Bolas Es besteht keine Notwendigkeit zu verwenden std::pair; Jeder geeignete paarartige Typ mit den entsprechenden Konvertierungen reicht aus, und range-v3 definiert einen solchen paarartigen Typ. Im Ausschuss gefiel es der LEWG nicht, der Standardbibliothek einen Typ hinzuzufügen, der fast, aber nicht ganz std::pair, ob normativ oder nicht, war, ohne zuvor die Vor- und Nachteile einer einfachen std::pairArbeit sorgfältig zu prüfen .
Eric Niebler
3
tuple, pair, tomato, to- MAH- to. pairhat diese nette Funktion, mit der Sie auf die Elemente mit .firstund zugreifen können .second. Strukturierte Bindungen helfen bei der Unbeholfenheit, mit tuples zu arbeiten, aber nicht bei allen.
Eric Niebler