Re[9]: Можно ли и как канонично получить из итератора тип эл
От: B0FEE664  
Дата: 22.04.25 17:28
Оценка: -1 :)
Здравствуйте, rg45, Вы писали:

BFE>>Кстати, что насчёт std::reverse ? Ему двунаправленные итераторы надобны.

R>А это-то ту при чём? Это уже следующая категория итераторов.
Так в этом загвоздка: output iteratorы написать легко, в отличии от.

  Вот пример от копилота
Напиши на C++ 17 класс iterator, который поддерживает проход по символам (а не по байтам) по строке закодированной в utf8

#include <iostream>
#include <cstddef>
#include <cstring>
#include <iterator>

// Итератор для прохода по символам в строке UTF-8
class 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 10xxxxxx
            return ((lead & 0x1F) << 6) |
                   (static_cast<unsigned char>(s[1]) & 0x3F);
        } else if ((lead >> 4) == 0xE) {
            // Трёхбайтовая последовательность: 1110xxxx 10xxxxxx 10xxxxxx
            return ((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 10xxxxxx
            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;
    }
};

// Обёртка над 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; // 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; // На всякий случай
    }

    // Декодирование 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"
  ответ с ошибкой, но...
#include <iostream>
#include <string>
#include <iterator>
#include <stdexcept>

// Итератор для прохода по символам 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;
};

// Возвращает прокси‑объект для чтения/записи текущего символа.
proxy operator*() const {
return proxy(str, pos);
}

// Префиксный инкремент: вычисляем длину текущего символа и смещаем позицию.
utf8_iterator& operator++() {
if (pos < str->size()) {
pos += get_char_length(str->data() + pos);
}
return *this;
}

// Постфиксный инкремент.
utf8_iterator operator++(int) {
utf8_iterator temp = *this;
++(*this);
return temp;
}

bool operator==(const utf8_iterator& other) const {
return (str == other.str) && (pos == other.pos);
}

bool operator!=(const utf8_iterator& other) const {
return !(*this == other);
}

// Определяет число байт, занимаемых символом UTF‑8, анализируя первый байт.
static int get_char_length(const char* s) {
unsigned char lead = static_cast<unsigned char>(*s);
if (lead < 0x80) return 1; // ASCII: 0xxxxxxx
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; // На всякий случай
}

// Декодирует UTF‑8 последовательность в Unicode‑кодовую точку (char32_t).
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.
// Результат записывается в buffer; возвращается число записанных байт.
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:
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)) {}

// begin/end возвращают итераторы, основанные на базовой строке.
utf8_iterator begin() {
return utf8_iterator(&data, 0);
}

utf8_iterator end() {
return utf8_iterator(&data, data.size());
}

// Для вывода результирующей строки.
const std::string& str() const { return data; }

private:
std::string data;
};

//
// Пример использования
//
int main() {
// Исходная строка, содержащая кириллический текст, закодированный в UTF‑8.
std::string text = u8"Привет, мир!";
utf8_string_view view(text);

std::cout << "Исходная строка: " << view.str() << "\n";

// Пример 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ы — они как утконосы: однопроходные.
И каждый день — без права на ошибку...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.