Verbieten die Iteratorkategorien von C ++ das Schreiben eines UTF-8-Iteratoradapters?


8

Ich habe an einem UTF-8-Iteratoradapter gearbeitet. Damit meine ich einen Adapter, der einen Iterator zu einer charoder unsigned charSequenz in einen Iterator zu einer char32_tSequenz verwandelt . Meine Arbeit hier wurde von diesem Iterator inspiriert, den ich online gefunden habe .

Als ich jedoch den Standard durchgesehen habe, als ich mit meiner eigenen Implementierung begann, wurde mir klar: Es scheint nicht möglich zu sein, einen solchen Adapter zu implementieren, während er den Anforderungen entspricht, die C ++ an Iteratoren stellt.

Könnten Sie beispielsweise einen UTF-8-Iterator erstellen, der die InputIterator-Anforderungen erfüllt? Ja, aber nur solange der Iterator, den Sie erhalten , selbst kein InputIterator ist. Warum?

Weil InputIterator die Fähigkeit erfordert, denselben Iterator mehr als einmal zu dereferenzieren. Sie können auch mehrere Kopien dieses Iterators dereferenzieren, sofern alle gleich sind.

Das Dereferenzieren eines UTF-8-Iteratoradapters erfordert natürlich sowohl das Dereferenzieren als auch das potenzielle Inkrementieren des Basisiterators. Und wenn dieser Iterator ein InputIterator ist, können Sie den ursprünglichen Wert nicht zurückerhalten, nachdem Sie ihn erhöht haben. Und die Tatsache, dass Kopien funktionieren müssen, bedeutet, dass Sie keine lokal speichern können char32_t, die den zuvor dekodierten Wert darstellt. Du hättest das tun können:

auto it = ...
auto it2 = it; //Copies an empty `char32_t`.
*it;           //Accesses base iterator, storing `it.ch`.
*it;           //Doesn't access the base iterator; simply returns `it.ch`.
*it2;          //Cannot access `it.ch`, so must access base iterator.

OK, gut, Sie können also keine InputIterators verwenden. Aber was ist mit ForwardIterator? Ist es möglich, einen ForwardIterator-Adapter zu erstellen, der ForwardIterators über UTF-8-Zeichenfolgen anpassen kann?

Das ist auch problematisch, weil die Operation *itist erforderlich zu produzieren value_type&oder const value_type&. InputIterators können alles ausspucken, was konvertierbar ist value_type, aber a ForwardIteratorist erforderlich, um eine tatsächliche Referenz bereitzustellen [forward.iterators] /1.3:

Wenn Xes sich um einen veränderlichen Iterator handelt, referencewird auf verwiesen T. Wenn Xes sich um einen konstanten Iterator handelt, referencewird auf verwiesenconst T

Der einzige Rückgriff besteht darin, dass jeder solche Iterator ein mit sich herumträgt char32_t, das nur existiert, um den Speicher für diese Referenz bereitzustellen. Und selbst dann muss dieser Wert jedes Mal aktualisiert werden, wenn die Iteratorinstanz inkrementiert und dereferenziert wird. Dies macht die alte Referenz effektiv ungültig, und der Standard erlaubt dies nicht explizit (eine Ungültigmachung kann nur erfolgen, wenn ein Iterator zerstört wird oder wenn der Container dies sagt).

Der oben genannte Code, den ich online gefunden habe, ist aus diesem Grund nicht gültig, da er eher einen uint32_t(vor C ++ 11 geschriebenen) Wert als eine richtige Referenz zurückgibt .

Gibt es hier einen Rückgriff? Habe ich etwas im Standard übersehen oder eine Implementierungstechnik, mit der ich diese Probleme umgehen könnte? Oder ist dies mit dem aktuellen Wortlaut der Norm einfach nicht möglich?

Hinweis: Das Seltsame ist, dass es möglich zu sein scheint, einen konformen OutputIterator für die UTF-8-Konvertierung zu schreiben. Das heißt, ein Typ, der char32_tUTF-8 in einen charoder unsigned charOutputIterator nimmt und schreibt .


3
Es ist bekannt, dass der Wortlaut von ForwardIteratornicht gut zu Proxy-Iteratoren passte , wie sie vector<bool>möglich waren. Es gab einen bekannten Artikel von Herb Sutter aus dem Jahr 1999 , in dem erklärt wurde, warum diese Entscheidung getroffen wurde. In der Neuzeit gab es einen Trend, dieses Thema zu überdenken. Ich finde einen von Eric Niebler . Es könnte mehr geben; Es könnte sogar einige geben, die Herb Sutter selbst in einigen C ++ - Vorschlägen geschrieben hat.
Rwong

Können Sie mit InputIterator den Cache nicht lesen, bevor Sie den Iterator dereferenzieren?
user253751

@immibis: Ähm, lesen Sie welchen Cache? Das Lesen aus dem Eingabe-Iterator, bevor der Benutzer tatsächlich dereferenziert, kann dazu führen, dass ich auf ungültige Iteratoren zugreife, da ein Iterator nicht unbedingt weiß, wo sich das Ende des Bereichs befindet. Wenn Sie also einen Iterator inkrementieren, bedeutet dies nicht, dass es in Ordnung ist, ihn zu dereferenzieren. Denken Sie auch an den Punkt, den ich beim Kopieren von InputIterators angesprochen habe: Wenn Sie zwei Kopien desselben Eingabe-Iterators dereferenzieren, sollten Sie denselben Wert erhalten.
Nicol Bolas

Antworten:


2

Ich denke die kurze Antwort ist ja. Ein Iteratoradapter, der UTF-8 decodiert (und im Allgemeinen möglicherweise mehrere Eingabeelemente benötigt, um ein einzelnes Ausgabeelement zu erzeugen), muss auf einen Iterator gelegt werden, der (mindestens) BidirectionalIterator modelliert.

Beachten Sie, dass dies voraussetzt, dass Sie nur einen konstanten Iterator möchten (dh Sie lesen nur UTF-8 von der Eingabe, schreiben UTF-8 nicht in die zugrunde liegende Sammlung). Wenn Sie das Schreiben unterstützen möchten, werden die Dinge in Eile viel hässlicher. Wenn Sie auf UTF-32-Ebene von einem Wert zum anderen wechseln, kann dies leicht zu einer UTF-8-Codierung mit einer anderen Größe führen. Sie müssen also vorbereitet sein Einfügen / Löschen von Elementen in der Mitte der zugrunde liegenden Sammlung, wenn Sie das Schreiben unterstützen möchten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.