Вот у меня есть класс UtfIterator, который при разыменовании возвращает 32ух-битный Unicode символ, разбирая UTF8 последовательность по char указателю, который хранит у себя, и соответственно, всякие игры c &*it выдадут лажу.
Также у меня в те же функции может быть передан итератор от вектора char или строки, или вообще голые указатели.
Я хочу из любого типа итераторов одинаковым способом получить const char*.
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Вот у меня есть класс UtfIterator, который при разыменовании возвращает 32ух-битный Unicode символ, разбирая UTF8 последовательность по char указателю, который хранит у себя, и соответственно, всякие игры c &*it выдадут лажу.
M>Также у меня в те же функции может быть передан итератор от вектора char или строки, или вообще голые указатели.
M>Я хочу из любого типа итераторов одинаковым способом получить const char*.
M>Есть ли для этого каноничное решение?
Чтобы продать что-нибудь ненужное, нужно сначала купить что-нибудь ненужное, а у нас денег нет
Чтобы откуда ни будь получить const char* он там изначально должен быть, а у тебя там 32ух-битный Unicode символ. Вот его вполне можно и получить
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
Здравствуйте, vopl, Вы писали:
V>Чтобы откуда ни будь получить const char* он там изначально должен быть, а у тебя там 32ух-битный Unicode символ. Вот его вполне можно и получить
У меня в итераторе const char* хранится, 32ух-битный Unicode символ собирается по UTF8-строке и при разыменовании возвращается по значению
Здравствуйте, Marty, Вы писали:
M>Здравствуйте!
M>Вот у меня есть класс UtfIterator, который при разыменовании возвращает 32ух-битный Unicode символ, разбирая UTF8 последовательность по char указателю, который хранит у себя, и соответственно, всякие игры c &*it выдадут лажу.
M>Также у меня в те же функции может быть передан итератор от вектора char или строки, или вообще голые указатели.
M>Я хочу из любого типа итераторов одинаковым способом получить const char*.
M>Есть ли для этого каноничное решение?
Ну так конечно, у тебя же последняя перегрузка невыводимая в принципе. Ты прикинь, одновременно 100500 классов контейнеров могут иметь одинаковые типы итераторов и какой тип контейнера должен при этом вывестись?
Здравствуйте, rg45, Вы писали:
M>>Никак не понимает, что я хочу. Как объяснить?
R>Ну так конечно, у тебя же последняя перегрузка невыводимая в принципе.
Ну я не настоящий сварщик
R>Ты прикинь, одновременно 100500 классов контейнеров могут иметь одинаковые типы итераторов и какой тип контейнера должен при этом вывестись?
А мне на тип контейнера наплевать, мне интересен тип, который получается при разыменовании ContainerType::iterator, при этом, если мне дали итератор не последовательногопроизвольного доступа, и я начал с ним работать — эта проблема меня не волнует, я не хочу парится с детектом такой ситуации.
Хотя, был наверное не прав в формулировках. Я исхожу из той концепции, что итератор либо указывает на реальную память, и при разыменовании возвращает что-то, с чем можно сделать &*it, и какие-то другие типы итераторов, которые надо отдельно обрабатывать.
Тут подумалось, можно попробовать:
— является ли итератор простым указателем
— можно ли сделать &*it
— огород для других типов итераторов
но не уверен, что компилятор меня поймёт, да и как реализовать, пока не очень понимаю.
Наверное, надо копнуть на тему enable_if c decltype/decay по типу *it
R>На С++20 эта задача решается легко и изящно:
А на С++17?
Мне бы хотелось привести к указателю на char
— итератор любого стандартного контейнера с произвольным доступом, который содержит элементы типа char или интегрального тип такого же размера (по размеру — желательно, но не обязательно)
— сам char* или const char*
— свой итератор, который при разыменовании возвращает не ссылку на элемент по хранимому в нем char-указателю, а произвольное значение by value. У последнего типа итератора есть метод rawPtr
R>Можно сделать аналог и на SFINAE, но там чуть больше пыли глотать придётся.
Ну, намекни, куда копать, мне пока не понятно. Сделал совсем тупо для базовых кейсов — тоже не прокатило:
Здравствуйте, Marty, Вы писали:
R>>Можно сделать аналог и на SFINAE, но там чуть больше пыли глотать придётся. M>Ну, намекни, куда копать, мне пока не понятно. Сделал совсем тупо для базовых кейсов — тоже не прокатило:
Ну, как-то так. И обрати внимание, если ты хочешь, чтоб работал ADL, перегрузку rawConstCharPtrFromIterator для UtfInputIterator нужно внести в неймспейс marty::utf.
Здравствуйте, Marty, Вы писали:
M>Можно ли и как канонично получить из итератора тип элемента последовательности? M>Я хочу из любого типа итераторов одинаковым способом получить const char*.
Это две совершенно разные задачи. Вторая решаема (я показал
Первая же задача нерешаема в принципе. Давай рассмотрим такой псевдокод:
// Имеем несколько классов контейнеров.
// Каждый класс содержит внутри определения своих итераторов.
// И разумеется уникальность типов итераторов никто не гарантирует.struct Containter1 { using iterator = int*; };
struct Containter2 { using iterator = int*; };
struct Containter3 { using iterator = int*; };
// Допустим, что по типу итератора можно вывести тип контейнера.template <typename ContainerType>
void foo(ContainerType::iterator);
int main()
{
// Попоробуем заюзать это дело.
Container1::iterator it1;
Container2::iterator it2;
Container3::iterator it3;
// Вот в этом месте самое время обратить внимание на то,
// что объекты it1, it2, it3 имеют один и тот же тип - int*.
// И каким образом компилятор должен понять, что в следующих
// случаях должны быть выведены различные типы контейнеров и какие именно?
foo(it1);
foo(it2);
foo(it3);
}
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>>>Можно сделать аналог и на SFINAE, но там чуть больше пыли глотать придётся. M>>Ну, намекни, куда копать, мне пока не понятно. Сделал совсем тупо для базовых кейсов — тоже не прокатило:
R>Ну, как-то так. И обрати внимание, если ты хочешь, чтоб работал ADL, перегрузку rawConstCharPtrFromIterator для UtfInputIterator нужно внести в неймспейс marty::utf.
R>https://coliru.stacked-crooked.com/a/456031d81cb952ce
Здравствуйте, Marty, Вы писали:
M>Я хочу из любого типа итераторов одинаковым способом получить const char*. M>Есть ли для этого каноничное решение?
Я бы добавил итератору метод
const char* base()
(по аналогии с std::reverse_iterator)
Я так понимаю, все UtfIterator'ы только константные и поддержки *it = 'a' не будет? Вот без этого не интересно.
И каждый день — без права на ошибку...
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
Здравствуйте, B0FEE664, Вы писали:
M>>Я хочу из любого типа итераторов одинаковым способом получить const char*. M>>Есть ли для этого каноничное решение? BFE>Я бы добавил итератору метод const char* base() (по аналогии с std::reverse_iterator)
Ну, rawPtr() таки возвращает не то, что base() в std::reverse_iterator, и я не хочу навешивать на стандартное имя метода то, что оно не делает — проверки по SFINAE или новомодные концепты могут детектить не то, что бы им хотелось на самом деле.
Но begin()/end() есть, мой итератор может выглядеть как range контейнер, от текущего указателя на char'ы до заданного конца
BFE>Я так понимаю, все UtfIterator'ы только константные и поддержки *it = 'a' не будет? Вот без этого не интересно.
Для вывода есть UtfOutputIterator, из InputIterator в OutputIterator можно скопировать как while(inB!=inE) *outIt++ = *inB++; , так и при помощи std алгоритмов: std::copy(inB, inE, outIt), или, при нужде что-то поменять, std::transform, как вариант.
Модифицировать исходные строки, вставляя/заменяя туда/там Unicode символы, которые могут иметь длину от одного до шести байт в UTF-8 — не вижу смысла — несколько замен/вставок в начале/середине строки могут как привести к переаллокации, так и гарантированно приведут к перемещениям хвостов контейнеров, в которых это хранится. Овчинка выделки не стоит, и даже вредна. Лучше для результата зарезервировать место один раз, с возможным запасом на результат модификации, это будет эффективнее.
Здравствуйте, rg45, Вы писали:
BFE>>Я так понимаю, все UtfIterator'ы только константные и поддержки *it = 'a' не будет? Вот без этого не интересно. R>Ну почему, само название UtfInputIterator намекает на существование UtfOutputIterator.
OutputIterator — это относительно просто, а вот изменение по месту — нетривиальная задача, от воплощения которой Marty тут же отказался. По работе я не сталкиваюсь с редакторами текста, так что для меня задача не актуальна и я не могу оценить востребованность такой операции, так что может оно и не надо...
И каждый день — без права на ошибку...
Re[4]: Можно ли и как канонично получить из итератора тип эл
Здравствуйте, B0FEE664, Вы писали:
BFE>OutputIterator — это относительно просто, а вот изменение по месту — нетривиальная задача, от воплощения которой Marty тут же отказался. По работе я не сталкиваюсь с редакторами текста, так что для меня задача не актуальна и я не могу оценить востребованность такой операции, так что может оно и не надо...
Здравствуйте, rg45, Вы писали:
BFE>>OutputIterator — это относительно просто, а вот изменение по месту — нетривиальная задача, от воплощения которой Marty тут же отказался. По работе я не сталкиваюсь с редакторами текста, так что для меня задача не актуальна и я не могу оценить востребованность такой операции, так что может оно и не надо... R>Так output iterator requirements включают в себя то самое изменение по месту: R>
R>A LegacyOutputIterator is a LegacyIterator that can write to the pointed-to element.
R>Это же его основной сценарий использования. Без этого стандартные алгоритмы просто не работали бы.
Изменение по месту я понимаю так:
*it = 'a';
*it = 'b';
результат: "...b...", а не "...ab...".
И каждый день — без права на ошибку...
Re[6]: Можно ли и как канонично получить из итератора тип эл
Здравствуйте, B0FEE664, Вы писали:
BFE>Изменение по месту я понимаю так: BFE>
BFE>*it = 'a';
BFE>*it = 'b';
BFE>
BFE>результат: "...b...", а не "...ab...".
К результату "ab" могут привести, разве что, такие специальные виды итераторов, как back_insert_iterator и ostream_iterator. Но эти операторы — это особый случай — у них операция инкремента, хоть и существует формально, но не имеет никакого эффекта. У этих итераторов операции *it = 'a' и *it++ = 'a' имеют один и тот же результат.
В самом же общем случае итератор является обобщением указателя и для того, чтобы получить последовательность ("ab"), итератор нужно инкрементить. Что и делают алгоритмы тип std::copy, std::fill, etc. А без этого инкремента это и будет многократное изменение одного и того же элемента (изменение по месту, как ты это называешь).
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали: BFE>>Изменение по месту я понимаю так: BFE>>
BFE>>*it = 'a';
BFE>>*it = 'b';
BFE>>
BFE>>результат: "...b...", а не "...ab...". R>К результату "ab" могут привести, разве что, только такие особые виды итераторов, как back_insert_iterator и ostream_iterator. Но даже у этих операторов существует операция инкремента (просто она пустая).
вот именно
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
int main()
{
std::vector<char> v;
std::back_insert_iterator<std::vector<char>> it(v);
*it = 'a';
*it = 'b';
for (auto n : v)
std::cout << n << ' ';
std::cout << '\n';
}
R>В самом же общем случае итератор является обобщением указателя и для того, чтобы получить последовательность ("ab"), итератор нужно инкрементить. Что и делают алгоритмы тип std::copy, std::fill, etc. А без этого инкремента это и будет многократное изменение одного и того же элемента (изменение по месту, как ты это называешь).
Кстати, что насчёт std::reverse ? Ему двунаправленные итераторы надобны.
И каждый день — без права на ошибку...
Re[8]: Можно ли и как канонично получить из итератора тип эл
Здравствуйте, B0FEE664, Вы писали: R>>К результату "ab" могут привести, разве что, только такие особые виды итераторов, как back_insert_iterator и ostream_iterator. Но даже у этих операторов существует операция инкремента (просто она пустая). BFE>
Ну, по-моему, ты просто меняешь местами общий и частный случай. Просто это особый вид итераторов, для которго, что *it = 'a', что *it++ = 'a' — это без разницы. Для общего случая это ведь не так. А общий случай — это такой, который является обобшением указателя (как и сказано в стандарте). Т.е. при многократной записи без инкремента будет происходить многократная модификация одного и того же объекта (или изменение по месту, как ты это обозначил). BFE>Кстати, что насчёт std::reverse ? Ему двунаправленные итераторы надобны.
А это-то ту при чём? Это уже следующая категория итераторов. Вопрос же был в чём — можно ли из названия "OutputIterator" ожидать модификации адресуемых итератором данных (изменения по месту). Ответ: да, в общем случае можно. Я выше даже ссылку приводил, где это прямо сказано.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали: BFE>>Кстати, что насчёт std::reverse ? Ему двунаправленные итераторы надобны. R>А это-то ту при чём? Это уже следующая категория итераторов.
Так в этом загвоздка: output iteratorы написать легко, в отличии от.
Вот пример от копилота
Напиши на C++ 17 класс iterator, который поддерживает проход по символам (а не по байтам) по строке закодированной в utf8
#include <iostream>
#include <cstddef>
#include <cstring>
#include <iterator>
// Итератор для прохода по символам в строке UTF-8class utf8_iterator {
public:
using value_type = char32_t;
using difference_type = ptrdiff_t;
using iterator_category = std::input_iterator_tag;
using pointer = const char32_t*;
using reference = const char32_t&;
// Конструктор принимает указатель на начало UTF-8 последовательности.explicit utf8_iterator(const char* ptr) : current(ptr) {}
// Префиксный инкремент: перемещается к следующему символу.
utf8_iterator& operator++() {
if (current && *current != '\0') {
current += get_char_length(current);
}
return *this;
}
// Постфиксный инкремент.
utf8_iterator operator++(int) {
utf8_iterator temp = *this;
++(*this);
return temp;
}
// Сравнение итераторов (сравниваются указатели).bool operator==(const utf8_iterator &other) const {
return current == other.current;
}
bool operator!=(const utf8_iterator &other) const {
return !(*this == other);
}
// Оператор разыменования возвращает Unicode‑кодовую точку, полученную из текущей позиции.
char32_t operator*() const {
return decode_utf8(current);
}
private:
const char* current = nullptr;
// Определяет длину текущего символа в байтах, исходя из первого байта.static int get_char_length(const char* s) {
unsigned char lead = static_cast<unsigned char>(*s);
if (lead < 0x80) return 1; // 0xxxxxxx — однобайтовый символ (ASCII)else if ((lead >> 5) == 0x6) return 2; // 110xxxxx — начало двухбайтовой последовательностиelse if ((lead >> 4) == 0xE) return 3; // 1110xxxx — начало трёхбайтовой последовательностиelse if ((lead >> 3) == 0x1E) return 4; // 11110xxx — начало четырёхбайтовой последовательностиreturn 1; // На всякий случай — считаем как 1 байт
}
// Декодирует символ UTF-8 (кодовая точка) из последовательности, начиная с s.static char32_t decode_utf8(const char* s) {
unsigned char lead = static_cast<unsigned char>(*s);
if (lead < 0x80) {
return lead;
} else if ((lead >> 5) == 0x6) {
// Двухбайтовая последовательность: 110xxxxx 10xxxxxxreturn ((lead & 0x1F) << 6) |
(static_cast<unsigned char>(s[1]) & 0x3F);
} else if ((lead >> 4) == 0xE) {
// Трёхбайтовая последовательность: 1110xxxx 10xxxxxx 10xxxxxxreturn ((lead & 0x0F) << 12) |
((static_cast<unsigned char>(s[1]) & 0x3F) << 6) |
(static_cast<unsigned char>(s[2]) & 0x3F);
} else if ((lead >> 3) == 0x1E) {
// Четырёхбайтовая последовательность: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxxreturn ((lead & 0x07) << 18) |
((static_cast<unsigned char>(s[1]) & 0x3F) << 12) |
((static_cast<unsigned char>(s[2]) & 0x3F) << 6) |
(static_cast<unsigned char>(s[3]) & 0x3F);
}
return 0;
}
};
// Обёртка над C-строкой для использования в range-based for.class utf8_string_view {
public:
explicit utf8_string_view(const char* s) : data(s) {}
utf8_iterator begin() const { return utf8_iterator(data); }
// Итератор "end" указывает на позицию конца строки (на символ '\0')
utf8_iterator end() const { return utf8_iterator(data + std::strlen(data)); }
private:
const char* data;
};
// Пример использованияint main() {
// Пример строки с русскими символами (UTF-8)const char* text = u8"Привет, мир!";
utf8_string_view view(text);
std::cout << "Кодовые точки символов строки:\n";
for (char32_t cp : view) {
std::cout << std::hex << "0x" << cp << " ";
}
std::cout << std::endl;
return 0;
}
Следующим шагом я запросил "В вышеприведённом примере измени operator*() так, чтобы итерируему строку можно было бы изменять через выражение *it = char32_symbol"
Хитрый копилот в ответ:
....
Обратите внимание, что в данном примере при изменении символа выполняется запись нового значения в ту же область памяти, предполагая, что новое Unicode‑значение при кодировании в UTF‑8 занимает то же число байт, что и исходное. Если число байт не совпадает, выбрасывается исключение. В реальном приложении можно реализовать поддержку перераспределения памяти или иное поведение для корректной замены символа.
....
code
#include <iostream>
#include <cstddef>
#include <cstring>
#include <iterator>
#include <stdexcept>
class utf8_iterator {
public:
using value_type = char32_t;
using difference_type = ptrdiff_t;
using iterator_category = std::input_iterator_tag;
// Теперь итератор работает с изменяемой строкой – указатель без const.explicit utf8_iterator(char* ptr) : current(ptr) {}
// Прокси-объект для разыменования, который позволяет читать символ (через неявное преобразование)
// и изменять его (через operator=).class proxy {
public:
explicit proxy(char* pos) : pos(pos) {}
// Неявное преобразование в char32_t для чтения текущего символа.operator char32_t() const {
return utf8_iterator::decode_utf8(pos);
}
// Оператор присваивания для записи нового символа.
// При этом новое значение кодируется в UTF‑8 и, для простоты,
// должно занимать столько же байт, сколько и исходное.
proxy& operator=(char32_t new_value) {
int old_len = utf8_iterator::get_char_length(pos);
char buffer[5] = {}; // Максимум 4 байта + завершающий ноль (не используется)int new_len = utf8_iterator::encode_utf8(new_value, buffer);
if (new_len != old_len) {
// Здесь можно реализовать поддержку изменения размера, но для простоты выбрасываем исключение.throw std::runtime_error("Длина кодировки нового символа не совпадает с длиной исходного символа");
}
for (int i = 0; i < new_len; ++i) {
pos[i] = buffer[i];
}
return *this;
}
private:
char* pos;
};
// Оператор разыменования возвращает proxy-объект, позволяющий читать и записывать символ.
proxy operator*() const {
return proxy(current);
}
// Префиксный инкремент – переходит к следующему символу UTF‑8.
utf8_iterator& operator++() {
if (current && *current != '\0') {
current += get_char_length(current);
}
return *this;
}
// Постфиксный инкремент.
utf8_iterator operator++(int) {
utf8_iterator temp = *this;
++(*this);
return temp;
}
bool operator==(const utf8_iterator &other) const {
return current == other.current;
}
bool operator!=(const utf8_iterator &other) const {
return !(*this == other);
}
// Определяет число байт в текущем символе, анализируя первый байт.static int get_char_length(const char* s) {
unsigned char lead = static_cast<unsigned char>(*s);
if (lead < 0x80)
return 1; // ASCIIelse if ((lead >> 5) == 0x6)
return 2; // 110xxxxxelse if ((lead >> 4) == 0xE)
return 3; // 1110xxxxelse if ((lead >> 3) == 0x1E)
return 4; // 11110xxxreturn 1; // На всякий случай
}
// Декодирование UTF‑8 последовательности в Unicode‑кодовую точку.static char32_t decode_utf8(const char* s) {
unsigned char lead = static_cast<unsigned char>(*s);
if (lead < 0x80) {
return lead;
} else if ((lead >> 5) == 0x6) {
return ((lead & 0x1F) << 6) |
(static_cast<unsigned char>(s[1]) & 0x3F);
} else if ((lead >> 4) == 0xE) {
return ((lead & 0x0F) << 12) |
((static_cast<unsigned char>(s[1]) & 0x3F) << 6) |
(static_cast<unsigned char>(s[2]) & 0x3F);
} else if ((lead >> 3) == 0x1E) {
return ((lead & 0x07) << 18) |
((static_cast<unsigned char>(s[1]) & 0x3F) << 12) |
((static_cast<unsigned char>(s[2]) & 0x3F) << 6) |
(static_cast<unsigned char>(s[3]) & 0x3F);
}
return 0;
}
// Функция кодирования Unicode‑кодовой точки в UTF‑8.
// Записывает результат в буфер и возвращает число записанных байт.static int encode_utf8(char32_t cp, char* buffer) {
if (cp < 0x80) {
buffer[0] = static_cast<char>(cp);
return 1;
} else if (cp < 0x800) {
buffer[0] = static_cast<char>(0xC0 | (cp >> 6));
buffer[1] = static_cast<char>(0x80 | (cp & 0x3F));
return 2;
} else if (cp < 0x10000) {
buffer[0] = static_cast<char>(0xE0 | (cp >> 12));
buffer[1] = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
buffer[2] = static_cast<char>(0x80 | (cp & 0x3F));
return 3;
} else if (cp < 0x110000) {
buffer[0] = static_cast<char>(0xF0 | (cp >> 18));
buffer[1] = static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
buffer[2] = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
buffer[3] = static_cast<char>(0x80 | (cp & 0x3F));
return 4;
}
throw std::runtime_error("Некорректная Unicode кодовая точка");
}
private:
char* current;
};
// Класс-обёртка для изменения строки, закодированной в UTF‑8, с поддержкой range-based for.class utf8_string_view {
public:
explicit utf8_string_view(char* s) : data(s) {}
utf8_iterator begin() const { return utf8_iterator(data); }
utf8_iterator end() const { return utf8_iterator(data + std::strlen(data)); }
private:
char* data;
};
//
// Пример использования
//int main() {
// Модифицируемая строка, закодированная в UTF‑8.
// Для теста изменим первую букву.char text[] = u8"Привет, мир!";
std::cout << "Исходная строка: " << text << "\n";
utf8_string_view view(text);
// Пример: заменим первую букву "П" (Unicode U+041F) на "З" (Unicode U+0417)for (auto it = view.begin(); it != view.end(); ++it) {
if (static_cast<char32_t>(*it) == U'П') {
*it = U'З';
break;
}
}
std::cout << "Изменённая строка: " << text << "\n";
return 0;
}
Третий раз я пошёл кликать золотую рыбку: "Напиши вышеприведённый пример с поддержкой перераспределения памяти. В качестве базового объекта строки используй std::string"
// Итератор для прохода по символам UTF‑8.
// Работает с модифицируемой строкой (std::string) посредством хранения указателя на неё и позиция (в байтах).
class utf8_iterator {
public:
using value_type = char32_t;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
// Конструктор принимает указатель на std::string и позицию в байтах.
utf8_iterator(std::string* s, size_t offset)
: str(s), pos(offset) {}
// Прокси‑объект, возвращаемый оператором разыменования.
// Он позволяет с одной стороны получить текущий символ (неявное преобразование в char32_t),
// а с другой — изменить его через присваивание.
class proxy {
public:
proxy(std::string* s, size_t offset) : str(s), pos(offset) {}
// Неявное преобразование в char32_t для чтения символа.
operator char32_t() const {
return utf8_iterator::decode_utf8(str->data() + pos);
}
// Оператор присваивания для изменения текущего символа.
// Если новое UTF‑8 представление занимает другое число байт,
// используется std::string::replace(), что приводит к перераспределению памяти.
proxy& operator=(char32_t new_value) {
const char* old_ptr = str->data() + pos;
int old_len = utf8_iterator::get_char_length(old_ptr);
char buffer[5] = {}; // 4 байта максимум для UTF‑8 символа + завершающий ноль.
int new_len = utf8_iterator::encode_utf8(new_value, buffer);
if(new_len == old_len) {
// Если длина не изменилась, просто перезаписываем соответствующие байты.
for (int i = 0; i < new_len; ++i)
(*str)[pos + i] = buffer[i];
} else {
// Если длина отличается – заменяем последовательность байт в строке. str->replace(pos, old_len, buffer, new_len);
}
return *this;
}
private:
std::string* str; // указатель на базовую строку
size_t pos; // текущая позиция (в байтах)
};
// Обёртка над std::string для поддержки range‑based for и модификаций UTF‑8 строки.
class utf8_string_view {
public:
// Конструктор принимает строку (будет храниться внутри объекта)
utf8_string_view(std::string s) : data(std::move(s)) {}
//
// Пример использования
//
int main() {
// Исходная строка, содержащая кириллический текст, закодированный в UTF‑8.
std::string text = u8"Привет, мир!";
utf8_string_view view(text);
// Пример 1: Замена первой буквы "П" (U+041F) на букву "З" (U+0417).
// Оба символа при UTF‑8 кодировке занимают 2 байта.
for (auto it = view.begin(); it != view.end(); ++it) {
if (static_cast<char32_t>(*it) == U'П') {
*it = U'З';
break;
}
}
std::cout << "После замены 'П' на 'З': " << view.str() << "\n";
// Пример 2: Замена символа "м" (U+043C, 2 байта) на смайлик "😀" (U+1F600, 4 байта).
// Здесь показывается перераспределение памяти: число байт изменяется.
for (auto it = view.begin(); it != view.end(); ++it) {
if (static_cast<char32_t>(*it) == U'м') {
*it = U'😀'; // Замена символа с различной длиной UTF‑8 представления
break;
}
}
std::cout << "После замены 'м' на '😀': " << view.str() << "\n";
return 0;
}
Забавно, что пример использования переписан так, чтобы проблему инвалидации end итератора было не видно. R> Вопрос же был в чём — можно ли из названия "OutputIterator" ожидать модификации адресуемых итератором данных (изменения по месту). Ответ: да, в общем случае можно. Я выше даже ссылку приводил, где это прямо сказано.
По ссылке прямо сказано, что нельзя (первое примечание из таблицы):
After this operation r is not required to be dereferenceable and any copies of the previous value of r are no longer required to be dereferenceable or incrementable.
output iteratorы — они как утконосы: однопроходные.
И каждый день — без права на ошибку...
Re[7]: Можно ли и как канонично получить из итератора тип эл
Здравствуйте, rg45, Вы писали:
R>К результату "ab" могут привести, разве что, такие специальные виды итераторов, как back_insert_iterator и ostream_iterator. Но эти операторы — это особый случай — у них операция инкремента, хоть и существует формально, но не имеет никакого эффекта. У этих итераторов операции *it = 'a' и *it++ = 'a' имеют один и тот же результат.
Ну, у меня UtfOutputIterator именно так себя и ведёт
Здравствуйте, Marty, Вы писали:
M>Ну, у меня UtfOutputIterator именно так себя и ведёт
Ну, это не только у тебя, я ж там приводил ещё примеры. Но, как минимум, могут быть варианты. В сам по себе output iterator concept автоинкремент при записи не заложен. Обычный неконстантый указатель также является подвидом output iterator, но он же не инкрементируется автоматически при записи. И стандартные алгоритмы типа std::copy выполняют явный инкремент итераторов не просто так ведь. Семантически у любого output итератора есть разыменование и инкремент, и это отдельные операции. То, что в некоторые классы итераторов инкремент встраивают в разыменование — это уже лайфхаки некторорых классов итераторов.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
M>>Ну, у меня UtfOutputIterator именно так себя и ведёт
R>Ну, это не только у тебя, я ж там приводил ещё примеры. Но, как минимум, могут быть варианты. В сам по себе output iterator concept автоинкремент при записи не заложен. Обычный неконстантый указатель также является подвидом output iterator, но он же не инкрементируется автоматически при записи. И стандартные алгоритмы типа std::copy выполняют явный инкремент итераторов не просто так ведь. Семантически у любого output итератора есть разыменование и инкремент, и это отдельные операции. То, что в некоторые классы итераторов инкремент встраивают в разыменование — это уже лайфхаки некторорых классов итераторов.