ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 17:21
Оценка:
За что я "люблю" эту концепцию STL так это за то, что наступаешь на грабли в самых казалось бы простых вещах!
Захотел написать обобщенный код, было:

    constexpr uint_t CalcSomething(const char* beg, const char* end) // ...
    constexpr uint_t CalcSomething(const char16_t* beg, const char16_t* end) // ...
    constexpr uint_t CalcSomething(const char32_t* beg, const char32_t* end) // ...
    constexpr uint_t CalcSomething(const wchar_t* beg, const wchar_t* end) // ...

Все отлично работает, был простой код. Не совсем гибко, а давайте еще обобщим и добавим что бы работало с любыми итераторами.
Проблема первая, как перегрузить итераторы только для перечисленных типов? Ну я ничего лучше не придумал чем (может кто подскажет как правильно):
    template<class Type>
    struct decay_with_const
    {    
        using NonRefType = std::remove_reference_t<Type>;

        using type = std::conditional_t<std::is_array_v<NonRefType>,
            std::add_pointer_t<std::remove_extent_t<NonRefType>>,
            std::conditional_t<std::is_function_v<NonRefType>,
            std::add_pointer_t<NonRefType>,
            std::remove_volatile_t<NonRefType>>>;
    };

    template<class _Ty>
    using decay_with_const_t = typename decay_with_const<_Ty>::type;

стандартный decay не подходит, так как убирает const.
    // Const iterator-like type check

    template<typename It, typename Value>
    struct const_iterator_of_type
    {
        static constexpr bool value = std::is_same_v<std::decay_t<decltype(*std::declval<It>())>, Value>;
    };

    template<typename It, typename Value>
    constexpr bool const_iterator_of_type_v = const_iterator_of_type<It, Value>::value;

Получилось так:
    template<typename It, std::enable_if_t<const_iterator_of_type_v<It, char>, int> = 0>
    constexpr uint_t CalcSomething(It beg, It end) // ...
    template<typename It, std::enable_if_t<const_iterator_of_type_v<It, char16_t>, int> = 0>
    constexpr uint_t CalcSomething(It beg, It end) // ...
    template<typename It, std::enable_if_t<const_iterator_of_type_v<It, char32_t>, int> = 0>
    constexpr uint_t CalcSomething(It beg, It end) // ...
    template<typename It, std::enable_if_t<const_iterator_of_type_v<It, wchar_t>, int> = 0>
    constexpr uint_t CalcSomething(It beg, It end) // ...

Вроде бы все работает, кроме последнего варианта с wchar_t, который на самом деле внутри ничего не делает, а вызывает нужную перегрузку:

    template<typename It, std::enable_if_t<const_iterator_of_type_v<It, wchar_t>, int>>
    constexpr uint_t CalcSomething(It beg, It end)
    {
        if constexpr (sizeof(wchar_t) == sizeof(char16_t))
            return CalcSomething<char16_t>(beg, end); // ????
        else if constexpr (sizeof(wchar_t) == sizeof(char32_t))
            return CalcSomething<char32_t>(beg, end); // ????
        else
            static_assert(!std::is_same_v<wchar_t, char16_t> && !std::is_same_v<wchar_t, char32_t>, "Unsuported wchar_t size!");

    }

И начаинается...
— преобразовать тип итераторов — я не могу
— перебразовать в указатели — я не могу, т.к. &*begin, &*end — нельзя разыменовывать если они указывают за конец данных (например у string_view) и т.д.

Вопрос:
Где как можно выйти в такой ситуации. Вроде задача элементарная, но времени отняла уже прилично. Так и чувствую себя как ослик бегущий за морковкой, ногу вытащишь, хвост увязнет и т.д.

Re: ненависть к итераторам
От: Voivoid Россия  
Дата: 25.12.20 19:18
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

V>Вопрос:

V>Где как можно выйти в такой ситуации. Вроде задача элементарная, но времени отняла уже прилично. Так и чувствую себя как ослик бегущий за морковкой, ногу вытащишь, хвост увязнет и т.д.

Ну, например, если ничего не упустил, как-то так ( https://ideone.com/m8igyh ):
template <typename Iter>
using iter_val_t = typename std::iterator_traits< Iter >::value_type;
 
template <typename Iter, typename T>
constexpr bool is_T_iter_v = std::is_same< iter_val_t< Iter >, T >::value;
 
template <typename Iter>
constexpr bool is_wchar_iter_v = std::is_same< iter_val_t< Iter >, wchar_t >::value;
 

template <typename Iter, std::enable_if_t < is_T_iter_v< Iter, char >, int > = 0 >
void CalcSomething( Iter begin, Iter end )
{
  std::cout << "i'm char\n";
}
 
template <typename Iter, std::enable_if_t < is_T_iter_v< Iter, char16_t > ||
  ( is_wchar_iter_v< Iter > && sizeof( wchar_t ) == sizeof( char16_t ) ), int > = 0 >
void CalcSomething( Iter begin, Iter end )
{
  std::cout << "i'm char16\n";
}
 
template <typename Iter, std::enable_if_t < is_T_iter_v< Iter, char32_t > ||
  ( is_wchar_iter_v< Iter > && sizeof( wchar_t ) == sizeof( char32_t ) ), int > = 0 >
void CalcSomething( Iter begin, Iter end )
{
  std::cout << "i'm char32\n";
}
Re: ненависть к итераторам
От: night beast СССР  
Дата: 25.12.20 19:38
Оценка:
Здравствуйте, Videoman, Вы писали:

V>За что я "люблю" эту концепцию STL так это за то, что наступаешь на грабли в самых казалось бы простых вещах!

V>Захотел написать обобщенный код, было:

V>
V>    constexpr uint_t CalcSomething(const char* beg, const char* end) // ...
V>    constexpr uint_t CalcSomething(const char16_t* beg, const char16_t* end) // ...
V>    constexpr uint_t CalcSomething(const char32_t* beg, const char32_t* end) // ...
V>    constexpr uint_t CalcSomething(const wchar_t* beg, const wchar_t* end) // ...
V>

V>Все отлично работает, был простой код. Не совсем гибко, а давайте еще обобщим и добавим что бы работало с любыми итераторами.
V>Проблема первая, как перегрузить итераторы только для перечисленных типов? Ну я ничего лучше не придумал чем (может кто подскажет как правильно):

попробуй через перегрузку (не проверял):

constexpr uint_t CalcSomething(I beg, I end, type_identity<char>) // ...
constexpr uint_t CalcSomething(I beg, I end, type_identity<char16_t>) // ...
constexpr uint_t CalcSomething(I beg, I end, type_identity<char32_t>) // ...


constexpr uint_t CalcSomething(I beg, I end) {
  return CalcSomething(beg, end, type_identity<typename std::decay<decltype(*beg)>::type>{})
}


V>стандартный decay не подходит, так как убирает const.


это же хорошо
Re: ненависть к итераторам
От: rg45 СССР  
Дата: 25.12.20 19:57
Оценка:
Здравствуйте, Videoman, Вы писали:

V>И начаинается...

V>- преобразовать тип итераторов — я не могу
V>- перебразовать в указатели — я не могу, т.к. &*begin, &*end — нельзя разыменовывать если они указывают за конец данных (например у string_view) и т.д.


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

V>Вопрос:

V>Где как можно выйти в такой ситуации. Вроде задача элементарная, но времени отняла уже прилично. Так и чувствую себя как ослик бегущий за морковкой, ногу вытащишь, хвост увязнет и т.д.

Мне кажется, основная проблема в том, что ты пытаешься cделать обобщение в привязке к типу, тогда как здесь было бы уместнее делать обобщение в привязке к размеру, если я правильно понимаю задачу. Тогда версия для wchar_t просто отпала бы за недадобностью:

template<typename Iterator>
using underlying_size = std::integral_constant<size_t, sizeof(*std::declval<Iterator>())>;

template<typename It, std::enable_if_t<underlying_size<It>::value = 1, int> = 0>
constexpr uint_t CalcSomething(It beg, It end) // ...
template<typename It, std::enable_if_t<underlying_size<It>::value = 2, int> = 0>
constexpr uint_t CalcSomething(It beg, It end) // ...
template<typename It, std::enable_if_t<underlying_size<It>::value = 4, int> = 0>
constexpr uint_t CalcSomething(It beg, It end) // ...
--
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 20:53
Оценка:
Здравствуйте, Voivoid, Вы писали:

V>Ну, например, если ничего не упустил, как-то так ( https://ideone.com/m8igyh ):

V>[ccode]
V>template <typename Iter>
V>using iter_val_t = typename std::iterator_traits< Iter >::value_type;

V>template <typename Iter, typename T>

V>constexpr bool is_T_iter_v = std::is_same< iter_val_t< Iter >, T >::value;

V>template <typename Iter>

V>constexpr bool is_wchar_iter_v = std::is_same< iter_val_t< Iter >, wchar_t >::value;

Ну не совсем так. Я хочу именно обобщение, т.е. должны приниматься и итераторы и голые указатели в этом и проблема. Но идею я понял, спасибо, попробую.
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 20:58
Оценка:
Здравствуйте, night beast, Вы писали:

NB>попробуй через перегрузку (не проверял):


NB>[ccode]

NB>constexpr uint_t CalcSomething(I beg, I end, type_identity<char>) // ...
NB>constexpr uint_t CalcSomething(I beg, I end, type_identity<char16_t>) // ...
NB>constexpr uint_t CalcSomething(I beg, I end, type_identity<char32_t>) // ...

Через перегрузку этих трех методов будет работать. Проблема в том что wchar_t это не алиас, а отдельный тип и он не будет кастится, или я не понял идею.

V>>стандартный decay не подходит, так как убирает const.


NB>это же хорошо


Итератор имеет семантику указателя и я тогда не смогу передавать не константные итераторы в функцию требующую константные.
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 21:00
Оценка:
Здравствуйте, rg45, Вы писали:

R>Мне кажется, основная проблема в том, что ты пытаешься cделать обобщение в привязке к типу, тогда как здесь было бы уместнее делать обобщение в привязке к размеру, если я правильно понимаю задачу. Тогда версия для wchar_t просто отпала бы за недадобностью:


R>
R>template<typename Iterator>
R>using underlying_size = std::integral_constant<size_t, sizeof(*std::declval<Iterator>())>;

R>template<typename It, std::enable_if_t<underlying_size<It>::value = 1, int> = 0>
R>constexpr uint_t CalcSomething(It beg, It end) // ...
R>template<typename It, std::enable_if_t<underlying_size<It>::value = 2, int> = 0>
R>constexpr uint_t CalcSomething(It beg, It end) // ...
R>template<typename It, std::enable_if_t<underlying_size<It>::value = 4, int> = 0>
R>constexpr uint_t CalcSomething(It beg, It end) // ...
R>


У меня закралось сомнение что я не стой стороны подхожу, но я не понял твоей идеи. т.е. будет срабатывать для всех типов имеющих размер 1,2,4. Если это так, то это совсем не то что мен нужно. Мне нужно только для char, char16_t, char32_t, wchar_t (для совместимости со старым кодом)
Re: ненависть к итераторам
От: reversecode google
Дата: 25.12.20 21:12
Оценка: +1
зачем разные функции пытаться сводить в одну ?
это напоминает по моему маерса(если не ошибаюсь) который на каком то с++ конфе делал доклад
про универсальную memcpy функцию
смысл которой тоже очень похож на ваши
и итог к которому он пришел после своих долгих раздумий и попыток — это невозможно сделать красиво
Re[3]: ненависть к итераторам
От: night beast СССР  
Дата: 25.12.20 21:14
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Через перегрузку этих трех методов будет работать. Проблема в том что wchar_t это не алиас, а отдельный тип и он не будет кастится, или я не понял идею.


не будет.
для него делается 4, или какая там по счету функция, в ней определяешь какую функцию из этих вызывать и прокидываешь параметром type_identity<нужный чар>, итераторы не трогая.
не зная, что конкретно этих функциях происходит сложно сказать, подойдет или нет такое решение

V>>>стандартный decay не подходит, так как убирает const.


NB>>это же хорошо


V>Итератор имеет семантику указателя и я тогда не смогу передавать не константные итераторы в функцию требующую константные.


компилятор тебе об этом сообщит.
тебя же не парит, что в том же std::copy нету енейбла по константности итератора
Re[4]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 21:22
Оценка:
Здравствуйте, night beast, Вы писали:

NB>не будет.

NB>для него делается 4, или какая там по счету функция, в ней определяешь какую функцию из этих вызывать и прокидываешь параметром type_identity<нужный чар>, итераторы не трогая.
NB>не зная, что конкретно этих функциях происходит сложно сказать, подойдет или нет такое решение

V>>>>стандартный decay не подходит, так как убирает const.


Ну не то. Мне не нравится когда код ломается совсем уж где-то в цепочках вызовов. Хочется добиться просто что бы перегрузка внешней функции либо срабатывала, либо нет.

NB>компилятор тебе об этом сообщит.

NB>тебя же не парит, что в том же std::copy нету енейбла по константности итератора

Честно — немного парит . По-моему это не правильно. Еще раз, у меня функции должны работать не только с итераторами, но и с голыми указателями и const корректность должна соблюдаться.
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 21:24
Оценка:
Здравствуйте, reversecode, Вы писали:

R>зачем разные функции пытаться сводить в одну ?

R>это напоминает по моему маерса(если не ошибаюсь) который на каком то с++ конфе делал доклад
R>про универсальную memcpy функцию
R>смысл которой тоже очень похож на ваши
R>и итог к которому он пришел после своих долгих раздумий и попыток — это невозможно сделать красиво

Согласен, я просто могу повторить код и на этом закончить но, на мой взгляд, шаблоны именно для того и нужны что бы не дублировать логику и помогать в этом разработчику. Я просто хочу удостовериться что я ничего не упустил и понять где граница.
Re: ненависть к итераторам
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 25.12.20 21:25
Оценка:
Здравствуйте, Videoman, Вы писали:

V>За что я "люблю" эту концепцию STL так это за то, что наступаешь на грабли в самых казалось бы простых вещах!

V>Захотел написать обобщенный код, было:

А я чет не парился никогда, всегда писал типа
template < typename IterType >
IterType my_algorithm( IterType b, IterType e ){...}
Маньяк Робокряк колесит по городу
Re[3]: ненависть к итераторам
От: Voivoid Россия  
Дата: 25.12.20 21:26
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Ну не совсем так. Я хочу именно обобщение, т.е. должны приниматься и итераторы и голые указатели в этом и проблема. Но идею я понял, спасибо, попробую.


Так и указатели тоже будут работать с точно таким же кодом: https://ideone.com/AvzGNO
int main() 
{
  const char* p = nullptr;
  CalcSomething( p, p );
}

Или о чем речь?
Re[3]: ненависть к итераторам
От: rg45 СССР  
Дата: 25.12.20 21:28
Оценка:
Здравствуйте, Videoman, Вы писали:


V>У меня закралось сомнение что я не стой стороны подхожу, но я не понял твоей идеи. т.е. будет срабатывать для всех типов имеющих размер 1,2,4. Если это так, то это совсем не то что мен нужно. Мне нужно только для char, char16_t, char32_t, wchar_t (для совместимости со старым кодом)


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

template<typename It, typename T>
using is_iterator_compatible = std::integral_constant<bool,
  std::is_same_v<T, typename std::iterator_traits<It>::value_type> ||
  (std::is_same_v<wchar_t, typename std::iterator_traits<It>::value_type> && sizeof(T) == sizeof(wchar_t))
>;

template<typename It, std::enable_if_t<is_iterator_compatible<It, char>::value, int> = 0>
constexpr uint_t CalcSomething(It beg, It end) // ...

template<typename It, std::enable_if_t<is_iterator_compatible<It, char16_t>::value, int> = 0>
constexpr uint_t CalcSomething(It beg, It end) // ...

template<typename It, std::enable_if_t<is_iterator_compatible<It, char32_t>::value, int> = 0>
constexpr uint_t CalcSomething(It beg, It end) // ...
--
Отредактировано 25.12.2020 21:31 rg45 . Предыдущая версия .
Re[3]: ненависть к итераторам
От: reversecode google
Дата: 25.12.20 21:29
Оценка:
по правильному — формализуйте логику всех трех в одной функции и ограничьте ее концептами
Re[5]: ненависть к итераторам
От: night beast СССР  
Дата: 25.12.20 21:30
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Ну не то. Мне не нравится когда код ломается совсем уж где-то в цепочках вызовов. Хочется добиться просто что бы перегрузка внешней функции либо срабатывала, либо нет.


ну, бывает и так что потом тратишь время разбираясь, почему не находит, вот же она...
Re[4]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 21:42
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>Ну тогда просто слегка допилить фильтр. Но общим остается то, что отдельная версия для wchar_t не нужна — whar_t должен автоматом подхватываться одной из трех других версий:


R>
R>template<typename It, typename T>
R>using is_iterator_compatible = std::integral_constant<bool,
R>  std::is_same_v<T, typename std::iterator_traits<It>::value_type> ||
R>  (std::is_same_v<wchar_t, typename std::iterator_traits<It>::value_type> && sizeof(T) == sizeof(wchar_t))
>>;

R>template<typename It, std::enable_if_t<is_iterator_compatible<It, char>::value, int> = 0>
R>constexpr uint_t CalcSomething(It beg, It end) // ...

R>template<typename It, std::enable_if_t<is_iterator_compatible<It, char16_t>::value, int> = 0>
R>constexpr uint_t CalcSomething(It beg, It end) // ...

R>template<typename It, std::enable_if_t<is_iterator_compatible<It, char32_t>::value, int> = 0>
R>constexpr uint_t CalcSomething(It beg, It end) // ...

R>


Да. Ну получается, в общем, это подход который предложил Voivoid. Так работает.
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 25.12.20 21:48
Оценка:
Здравствуйте, Marty, Вы писали:

M>А я чет не парился никогда, всегда писал типа

M>
template < typename IterType >
M>IterType my_algorithm( IterType b, IterType e ){...}


Ну ты описываешь самое начало, то с чего все начинают. А мне понадобилось обобщить итераторы и указатели, да так, что бы перегрузка не срабатывала когда не подходит. У тебя код самая "жадная" штука, которая принимает в себя абсолюьтно все и ломается уже в дебрях. Дальше остается только догадываться что пошло не так рассматривая простыни сообщении об ошибках. Я пытаюсь избежать такого подхода. Пытаюсь эмулировать концепты на с++17. Концепты для бедных, так сказать .
Re[3]: ненависть к итераторам
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 25.12.20 21:50
Оценка:
Здравствуйте, Videoman, Вы писали:

M>>А я чет не парился никогда, всегда писал типа

M>>
template < typename IterType >
M>>IterType my_algorithm( IterType b, IterType e ){...}


V>Ну ты описываешь самое начало, то с чего все начинают. А мне понадобилось обобщить итераторы и указатели, да так, что бы перегрузка не срабатывала когда не подходит. У тебя код самая "жадная" штука, которая принимает в себя абсолюьтно все и ломается уже в дебрях. Дальше остается только догадываться что пошло не так рассматривая простыни сообщении об ошибках. Я пытаюсь избежать такого подхода. Пытаюсь эмулировать концепты на с++17. Концепты для бедных, так сказать .


Обычно хватает, когда припирает, можно уже и по тяжелову со всякими enable_if или как там
Маньяк Робокряк колесит по городу
Re[4]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 26.12.20 12:10
Оценка:
Здравствуйте, Voivoid, Вы писали:

V>Так и указатели тоже будут работать с точно таким же кодом: https://ideone.com/AvzGNO

V>
V>int main() 
V>{
V>  const char* p = nullptr;
V>  CalcSomething( p, p );
V>}
V>

V>Или о чем речь?

Да придирки... просто насколько я понял std::iterator_traits не отличает константные итераторы от не константных. Как с std::iterator_traits сделать что-бы метод принимал итераторы/указатели только не константные, иначе говоря, что бы код выше не компилировался, а вот такой компилировался?
int main() 
{
  char* p1 = nullptr;
  CalcSomething( p1, p1 ); // OK
  const char* p2 = nullptr;
  CalcSomething( p2, p2 ); // Fail

}
Отредактировано 26.12.2020 12:11 Videoman . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.