Можно ли и как канонично получить из итератора тип элемента последовательности?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 17:54
Оценка:
Здравствуйте!

Вот у меня есть класс UtfIterator, который при разыменовании возвращает 32ух-битный Unicode символ, разбирая UTF8 последовательность по char указателю, который хранит у себя, и соответственно, всякие игры c &*it выдадут лажу.

Также у меня в те же функции может быть передан итератор от вектора char или строки, или вообще голые указатели.

Я хочу из любого типа итераторов одинаковым способом получить const char*.

Есть ли для этого каноничное решение?
Маньяк Робокряк колесит по городу
Re: Можно ли и как канонично получить из итератора тип элемента последовательнос
От: vopl Россия  
Дата: 17.04.25 18:22
Оценка:
Здравствуйте, Marty, Вы писали:

M>Здравствуйте!


M>Вот у меня есть класс UtfIterator, который при разыменовании возвращает 32ух-битный Unicode символ, разбирая UTF8 последовательность по char указателю, который хранит у себя, и соответственно, всякие игры c &*it выдадут лажу.


M>Также у меня в те же функции может быть передан итератор от вектора char или строки, или вообще голые указатели.


M>Я хочу из любого типа итераторов одинаковым способом получить const char*.


M>Есть ли для этого каноничное решение?


Чтобы продать что-нибудь ненужное, нужно сначала купить что-нибудь ненужное, а у нас денег нет
Чтобы откуда ни будь получить const char* он там изначально должен быть, а у тебя там 32ух-битный Unicode символ. Вот его вполне можно и получить
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 18:26
Оценка:
Здравствуйте, vopl, Вы писали:

V>Чтобы откуда ни будь получить const char* он там изначально должен быть, а у тебя там 32ух-битный Unicode символ. Вот его вполне можно и получить


У меня в итераторе const char* хранится, 32ух-битный Unicode символ собирается по UTF8-строке и при разыменовании возвращается по значению
Маньяк Робокряк колесит по городу
Re: Можно ли и как канонично получить из итератора тип элемента последовательнос
От: gyraboo  
Дата: 17.04.25 18:27
Оценка:
Здравствуйте, Marty, Вы писали:

M>Есть ли для этого каноничное решение?


Если речь про ООП, то там канон вообще против знаний о типе в рантайме.
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 18:30
Оценка:
Здравствуйте, gyraboo, Вы писали:

M>>Есть ли для этого каноничное решение?


G>Если речь про ООП, то там канон вообще против знаний о типе в рантайме.


Не знаю, что ты имеешь в виду, но я хочу в компайл тайме это сделать
Маньяк Робокряк колесит по городу
Re: Можно ли и как канонично получить из итератора тип элемента последовательнос
От: T4r4sB Россия  
Дата: 17.04.25 19:33
Оценка:
Здравствуйте, Marty, Вы писали:

M>Здравствуйте!


M>Вот у меня есть класс UtfIterator, который при разыменовании возвращает 32ух-битный Unicode символ, разбирая UTF8 последовательность по char указателю, который хранит у себя, и соответственно, всякие игры c &*it выдадут лажу.


M>Также у меня в те же функции может быть передан итератор от вектора char или строки, или вообще голые указатели.


M>Я хочу из любого типа итераторов одинаковым способом получить const char*.


M>Есть ли для этого каноничное решение?


Написать перегрузки?
void foo(UtfIterator);
void foo(std::vector<char>::iterator);
void foo(std::string::iterator);
void foo(const char*);

внутри там уже можно одну к другой привести чтоб код не дублировать
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 19:46
Оценка:
Здравствуйте, T4r4sB, Вы писали:

M>>Есть ли для этого каноничное решение?


TB>Написать перегрузки?


Пока так и решил сделать, но посчитал нужным спросить, вдруг есть стандартное решение
Маньяк Робокряк колесит по городу
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 19:50
Оценка:
Здравствуйте, T4r4sB, Вы писали:

M>>Есть ли для этого каноничное решение?


TB>Написать перегрузки?


Но вот такое не работает, компилятор не может понять, чего я от него хочу
//----------------------------------------------------------------------------
template<typename CharType>
const char* rawConstCharPtrFromIterator(marty::utf::UtfInputIterator<CharType> it)
{
    return (const char*)it.rawPtr();
}

//----------------------------------------------------------------------------
template<typename CharType>
const char* rawConstCharPtrFromIterator(const CharType *pstr)
{
    return (const char*)pstr;
}

//----------------------------------------------------------------------------
template<typename ContainerType>
const char* rawConstCharPtrFromIterator(typename ContainerType::const_iterator it)
{
    return (const char*)(&*it);
}
Маньяк Робокряк колесит по городу
Re: Можно ли и как канонично получить из итератора тип элемента последовательнос
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 20:13
Оценка:
Здравствуйте, Marty, Вы писали:

Попробовал по-разному, последнее такое:

//----------------------------------------------------------------------------
template<typename CharType>
const char* rawConstCharPtrFromIterator(marty::utf::UtfInputIterator<CharType> it)
{
    return (const char*)it.rawPtr();
}

//----------------------------------------------------------------------------
template<typename CharPointerType>
typename std::enable_if< std::is_pointer<CharPointerType>::value, const char* >::type
rawConstCharPtrFromIterator(CharPointerType pstr)
{
    return (const char*)pstr;
}

//----------------------------------------------------------------------------
template<typename ContainerType>
typename std::enable_if< has_const_iterator<ContainerType>::value, const char* >::type
rawConstCharPtrFromIterator(typename ContainerType::const_iterator it)
{
    return (const char*)(&*it);
}



Никак не понимает, что я хочу. Как объяснить?
Маньяк Робокряк колесит по городу
Re: Можно ли и как канонично получить из итератора тип элемента последовательнос
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 20:51
Оценка:
Здравствуйте, Marty, Вы писали:

Написал предельно тупо:
template<typename CharType>
const char* rawConstCharPtrFromIterator(marty::utf::UtfInputIterator<CharType> it)
{
    return (const char*)it.rawPtr();
}

template<typename CharType>
const char* rawConstCharPtrFromIterator(typename std::basic_string<CharType>::iterator it)
{
    return (const char*)(&*it);
}

template<typename CharType>
const char* rawConstCharPtrFromIterator(typename std::basic_string<CharType>::const_iterator it)
{
    return (const char*)(&*it);
}


Но компилятор меня всё равно не понимает
Маньяк Робокряк колесит по городу
Re[2]: Можно ли и как канонично получить из итератора тип эл
От: rg45 СССР  
Дата: 17.04.25 22:25
Оценка:
Здравствуйте, Marty, Вы писали:

M>Попробовал по-разному, последнее такое:


M>
M>//----------------------------------------------------------------------------
M>template<typename CharType>
M>const char* rawConstCharPtrFromIterator(marty::utf::UtfInputIterator<CharType> it)
M>{
M>    return (const char*)it.rawPtr();
M>}

M>//----------------------------------------------------------------------------
M>template<typename CharPointerType>
M>typename std::enable_if< std::is_pointer<CharPointerType>::value, const char* >::type
M>rawConstCharPtrFromIterator(CharPointerType pstr)
M>{
M>    return (const char*)pstr;
M>}

M>//----------------------------------------------------------------------------
M>template<typename ContainerType>
M>typename std::enable_if< has_const_iterator<ContainerType>::value, const char* >::type
M>rawConstCharPtrFromIterator(typename ContainerType::const_iterator it)
M>{
M>    return (const char*)(&*it);
M>}

M>



M>Никак не понимает, что я хочу. Как объяснить?


Ну так конечно, у тебя же последняя перегрузка невыводимая в принципе. Ты прикинь, одновременно 100500 классов контейнеров могут иметь одинаковые типы итераторов и какой тип контейнера должен при этом вывестись?

На С++20 эта задача решается легко и изящно:

template <typename T>
concept Dereferenceable = requires(T t) {{&*t} -> std::convertible_to<const void*>;};

template <typename T>
concept Pointer = Dereferenceable<T> and std::convertible_to<T, const void*>;

const char* rawConstCharPtrFromIterator(auto&& t)
requires requires { (const char*)t.rawPtr(); }
{
   return (const char*)t.rawPtr();
}

const char* rawConstCharPtrFromIterator(Pointer auto&& t)
{
   return (const char*)t;
}

const char* rawConstCharPtrFromIterator(Dereferenceable auto&& t)
{
   return (const char*)(&*t);
}


Можно сделать аналог и на SFINAE, но там чуть больше пыли глотать придётся.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 17.04.2025 22:29 rg45 . Предыдущая версия .
Re[3]: Можно ли и как канонично получить из итератора тип эл
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 17.04.25 23:04
Оценка:
Здравствуйте, 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, но там чуть больше пыли глотать придётся.


Ну, намекни, куда копать, мне пока не понятно. Сделал совсем тупо для базовых кейсов — тоже не прокатило:

//----------------------------------------------------------------------------
template<typename CharType>
const char* rawConstCharPtrFromIterator(marty::utf::UtfInputIterator<CharType> it)
{
    return (const char*)it.rawPtr();
}

template<typename CharType>
const char* rawConstCharPtrFromIterator(typename std::basic_string<CharType>::iterator it)
{
    return (const char*)(&*it);
}

template<typename CharType>
const char* rawConstCharPtrFromIterator(typename std::basic_string<CharType>::const_iterator it)
{
    return (const char*)(&*it);
}
Маньяк Робокряк колесит по городу
Отредактировано 17.04.2025 23:18 Marty . Предыдущая версия . Еще …
Отредактировано 17.04.2025 23:07 Marty . Предыдущая версия .
Re[4]: Можно ли и как канонично получить из итератора тип эл
От: rg45 СССР  
Дата: 18.04.25 07:47
Оценка: 12 (1)
Здравствуйте, Marty, Вы писали:

R>>Можно сделать аналог и на SFINAE, но там чуть больше пыли глотать придётся.

M>Ну, намекни, куда копать, мне пока не понятно. Сделал совсем тупо для базовых кейсов — тоже не прокатило:

Ну, как-то так. И обрати внимание, если ты хочешь, чтоб работал ADL, перегрузку rawConstCharPtrFromIterator для UtfInputIterator нужно внести в неймспейс marty::utf.

https://coliru.stacked-crooked.com/a/456031d81cb952ce

#include <cstdint>
#include <iostream>
#include <type_traits>

template<typename, typename = void>
struct IsDereferenceableToLvalue_ : std::false_type {};

template<typename T>
struct IsDereferenceableToLvalue_<T, std::void_t<decltype(&*std::declval<T>())>> : std::true_type {};

template<typename T>
inline constexpr bool IsDereferenceableToLvalue = IsDereferenceableToLvalue_<T>::value;

template<typename T>
inline constexpr bool IsPointer = std::is_pointer_v<std::decay_t<T>>;

namespace marty {
namespace utf {

template <typename CharType>
struct UtfInputIterator
{
   const CharType& operator*() const;
   const CharType* rawPtr() const {return reinterpret_cast<const CharType*>("marty::utf::UtfInputIterator"); };
};

template<typename CharType>
const char* rawConstCharPtrFromIterator(UtfInputIterator<CharType> it)
{
   return reinterpret_cast<const char*>(it.rawPtr());
}

} // namespace utf
} // namespace marty

template<typename CharPointerType>
const char* rawConstCharPtrFromIterator(CharPointerType* pstr)
{
   return reinterpret_cast<const char*>(pstr);
}

template <typename IteratorType, std::enable_if_t<
    IsDereferenceableToLvalue<IteratorType>
    and not IsPointer<IteratorType>
, int> = 0>
const char* rawConstCharPtrFromIterator(IteratorType it)
{
   return reinterpret_cast<const char*>(&*it);
}

int main()
{
   const char* const c = "const char*";
   std::string const s = "std::string";
   marty::utf::UtfInputIterator<uint32_t> const it{};

   std::cout << rawConstCharPtrFromIterator(c) << std::endl;
   std::cout << rawConstCharPtrFromIterator(s.begin()) << std::endl;
   std::cout << rawConstCharPtrFromIterator(it) << std::endl;
}
--
Справедливость выше закона. А человечность выше справедливости.
Re: Можно ли и как канонично получить из итератора тип элеме
От: rg45 СССР  
Дата: 18.04.25 08:36
Оценка: +1
Здравствуйте, Marty, Вы писали:

M>Можно ли и как канонично получить из итератора тип элемента последовательности?

M>Я хочу из любого типа итераторов одинаковым способом получить const char*.

Это две совершенно разные задачи. Вторая решаема (я показал
Автор: rg45
Дата: 18.04.25
вариант решения).

Первая же задача нерешаема в принципе. Давай рассмотрим такой псевдокод:

// Имеем несколько классов контейнеров.
// Каждый класс содержит внутри определения своих итераторов.
// И разумеется уникальность типов итераторов никто не гарантирует.

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);
}
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 18.04.2025 8:49 rg45 . Предыдущая версия . Еще …
Отредактировано 18.04.2025 8:38 rg45 . Предыдущая версия .
Отредактировано 18.04.2025 8:36 rg45 . Предыдущая версия .
Re[5]: Можно ли и как канонично получить из итератора тип эл
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 18.04.25 14:11
Оценка:
Здравствуйте, rg45, Вы писали:

R>>>Можно сделать аналог и на SFINAE, но там чуть больше пыли глотать придётся.

M>>Ну, намекни, куда копать, мне пока не понятно. Сделал совсем тупо для базовых кейсов — тоже не прокатило:

R>Ну, как-то так. И обрати внимание, если ты хочешь, чтоб работал ADL, перегрузку rawConstCharPtrFromIterator для UtfInputIterator нужно внести в неймспейс marty::utf.


R>https://coliru.stacked-crooked.com/a/456031d81cb952ce


Круто. Спасибо
Маньяк Робокряк колесит по городу
Re: Можно ли и как канонично получить из итератора тип элемента последовательнос
От: B0FEE664  
Дата: 18.04.25 17:13
Оценка:
Здравствуйте, Marty, Вы писали:

M>Я хочу из любого типа итераторов одинаковым способом получить const char*.

M>Есть ли для этого каноничное решение?
Я бы добавил итератору метод
const char* base()
(по аналогии с std::reverse_iterator)

Я так понимаю, все UtfIterator'ы только константные и поддержки *it = 'a' не будет? Вот без этого не интересно.
И каждый день — без права на ошибку...
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
От: rg45 СССР  
Дата: 18.04.25 17:37
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Я так понимаю, все UtfIterator'ы только константные и поддержки *it = 'a' не будет? Вот без этого не интересно.


Ну почему, само название UtfInputIterator намекает на существование UtfOutputIterator.
--
Справедливость выше закона. А человечность выше справедливости.
Re[2]: Можно ли и как канонично получить из итератора тип элемента последователь
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 18.04.25 18:18
Оценка:
Здравствуйте, 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 — не вижу смысла — несколько замен/вставок в начале/середине строки могут как привести к переаллокации, так и гарантированно приведут к перемещениям хвостов контейнеров, в которых это хранится. Овчинка выделки не стоит, и даже вредна. Лучше для результата зарезервировать место один раз, с возможным запасом на результат модификации, это будет эффективнее.
Маньяк Робокряк колесит по городу
Re[3]: Можно ли и как канонично получить из итератора тип элемента последователь
От: B0FEE664  
Дата: 22.04.25 10:30
Оценка:
Здравствуйте, rg45, Вы писали:

BFE>>Я так понимаю, все UtfIterator'ы только константные и поддержки *it = 'a' не будет? Вот без этого не интересно.

R>Ну почему, само название UtfInputIterator намекает на существование UtfOutputIterator.

OutputIterator — это относительно просто, а вот изменение по месту — нетривиальная задача, от воплощения которой Marty тут же отказался. По работе я не сталкиваюсь с редакторами текста, так что для меня задача не актуальна и я не могу оценить востребованность такой операции, так что может оно и не надо...
И каждый день — без права на ошибку...
Re[4]: Можно ли и как канонично получить из итератора тип эл
От: rg45 СССР  
Дата: 22.04.25 11:12
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>OutputIterator — это относительно просто, а вот изменение по месту — нетривиальная задача, от воплощения которой Marty тут же отказался. По работе я не сталкиваюсь с редакторами текста, так что для меня задача не актуальна и я не могу оценить востребованность такой операции, так что может оно и не надо...


Так output iterator requirements включают в себя то самое изменение по месту:

A LegacyOutputIterator is a LegacyIterator that can write to the pointed-to element.


Это же его основной сценарий использования. Без этого стандартные алгоритмы просто не работали бы.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 22.04.2025 11:17 rg45 . Предыдущая версия .
Re[5]: Можно ли и как канонично получить из итератора тип эл
От: B0FEE664  
Дата: 22.04.25 14:01
Оценка:
Здравствуйте, 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]: Можно ли и как канонично получить из итератора тип эл
От: rg45 СССР  
Дата: 22.04.25 14:58
Оценка:
Здравствуйте, 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. А без этого инкремента это и будет многократное изменение одного и того же элемента (изменение по месту, как ты это называешь).
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 22.04.2025 15:28 rg45 . Предыдущая версия . Еще …
Отредактировано 22.04.2025 15:26 rg45 . Предыдущая версия .
Отредактировано 22.04.2025 15:24 rg45 . Предыдущая версия .
Отредактировано 22.04.2025 15:19 rg45 . Предыдущая версия .
Отредактировано 22.04.2025 15:18 rg45 . Предыдущая версия .
Re[7]: Можно ли и как канонично получить из итератора тип эл
От: B0FEE664  
Дата: 22.04.25 15:33
Оценка: -1 :)
Здравствуйте, 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';
}

вывод a b

https://coliru.stacked-crooked.com/a/21f80d6127889c39

R>В самом же общем случае итератор является обобщением указателя и для того, чтобы получить последовательность ("ab"), итератор нужно инкрементить. Что и делают алгоритмы тип std::copy, std::fill, etc. А без этого инкремента это и будет многократное изменение одного и того же элемента (изменение по месту, как ты это называешь).
Кстати, что насчёт std::reverse ? Ему двунаправленные итераторы надобны.
И каждый день — без права на ошибку...
Re[8]: Можно ли и как канонично получить из итератора тип эл
От: rg45 СССР  
Дата: 22.04.25 15:59
Оценка:
Здравствуйте, B0FEE664, Вы писали:

R>>К результату "ab" могут привести, разве что, только такие особые виды итераторов, как back_insert_iterator и ostream_iterator. Но даже у этих операторов существует операция инкремента (просто она пустая).

BFE>
  вот именно
BFE>
BFE>#include <algorithm>
BFE>#include <iostream>
BFE>#include <iterator>
BFE>#include <vector>
 
BFE>int main()
BFE>{
BFE>    std::vector<char> v;
    
BFE>    std::back_insert_iterator<std::vector<char>> it(v);
    
BFE>    *it = 'a';
BFE>    *it = 'b';
 
BFE>    for (auto n : v)
BFE>        std::cout << n << ' ';
BFE>    std::cout << '\n';
BFE>}
BFE>

BFE>вывод a b
BFE>https://coliru.stacked-crooked.com/a/21f80d6127889c39


Ну, по-моему, ты просто меняешь местами общий и частный случай. Просто это особый вид итераторов, для которго, что *it = 'a', что *it++ = 'a' — это без разницы. Для общего случая это ведь не так. А общий случай — это такой, который является обобшением указателя (как и сказано в стандарте). Т.е. при многократной записи без инкремента будет происходить многократная модификация одного и того же объекта (или изменение по месту, как ты это обозначил).

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


А это-то ту при чём? Это уже следующая категория итераторов. Вопрос же был в чём — можно ли из названия "OutputIterator" ожидать модификации адресуемых итератором данных (изменения по месту). Ответ: да, в общем случае можно. Я выше даже ссылку приводил, где это прямо сказано.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 22.04.2025 16:06 rg45 . Предыдущая версия .
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ы — они как утконосы: однопроходные.
И каждый день — без права на ошибку...
Re[7]: Можно ли и как канонично получить из итератора тип эл
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 26.04.25 19:53
Оценка:
Здравствуйте, rg45, Вы писали:

R>К результату "ab" могут привести, разве что, такие специальные виды итераторов, как back_insert_iterator и ostream_iterator. Но эти операторы — это особый случай — у них операция инкремента, хоть и существует формально, но не имеет никакого эффекта. У этих итераторов операции *it = 'a' и *it++ = 'a' имеют один и тот же результат.


Ну, у меня UtfOutputIterator именно так себя и ведёт
Маньяк Робокряк колесит по городу
Re[8]: Можно ли и как канонично получить из итератора тип эл
От: rg45 СССР  
Дата: 27.04.25 07:12
Оценка:
Здравствуйте, Marty, Вы писали:

M>Ну, у меня UtfOutputIterator именно так себя и ведёт


Ну, это не только у тебя, я ж там приводил ещё примеры. Но, как минимум, могут быть варианты. В сам по себе output iterator concept автоинкремент при записи не заложен. Обычный неконстантый указатель также является подвидом output iterator, но он же не инкрементируется автоматически при записи. И стандартные алгоритмы типа std::copy выполняют явный инкремент итераторов не просто так ведь. Семантически у любого output итератора есть разыменование и инкремент, и это отдельные операции. То, что в некоторые классы итераторов инкремент встраивают в разыменование — это уже лайфхаки некторорых классов итераторов.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 27.04.2025 7:28 rg45 . Предыдущая версия . Еще …
Отредактировано 27.04.2025 7:26 rg45 . Предыдущая версия .
Re[9]: Можно ли и как канонично получить из итератора тип эл
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 27.04.25 09:57
Оценка:
Здравствуйте, rg45, Вы писали:

M>>Ну, у меня UtfOutputIterator именно так себя и ведёт


R>Ну, это не только у тебя, я ж там приводил ещё примеры. Но, как минимум, могут быть варианты. В сам по себе output iterator concept автоинкремент при записи не заложен. Обычный неконстантый указатель также является подвидом output iterator, но он же не инкрементируется автоматически при записи. И стандартные алгоритмы типа std::copy выполняют явный инкремент итераторов не просто так ведь. Семантически у любого output итератора есть разыменование и инкремент, и это отдельные операции. То, что в некоторые классы итераторов инкремент встраивают в разыменование — это уже лайфхаки некторорых классов итераторов.


Я в курсе
Маньяк Робокряк колесит по городу
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.