ненависть к итераторам
От: 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 . Предыдущая версия .
Re: ненависть к итераторам
От: so5team https://stiffstream.com
Дата: 26.12.20 12:50
Оценка:
Здравствуйте, Videoman

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

Сложилось впечатление, что у вас были обычные перегрузки для нескольких типов. Которые неявно подразумевали, что [begin, end) -- это непрерывный блок данных. Теперь вы захотели сделать свои шаблонные функции, которые бы принимали в качестве begin и end итераторы, дабы можно было работать с произвольными последовательностями, необязательно непрерывными.

Если так, то сходу напрашивается что-то такое:
namespace impl
{

template<typename Basic_Type>
struct calc_something_impl;

template<>
struct calc_something_impl<char> {
  template<typename First_It, typename Last_It>
  static void calc(First_It begin, Last_It end) {...}
};

template<>
struct calc_something_impl<char16_t> {
  template<typename First_It, typename Last_It>
  static void calc(First_It begin, Last_It end) {...}
};

... // Тоже самое и для других нужных вам типов.
} /* namespace impl */

template<typename First_It, typename Last_It>
void calc_something(First_It begin, Last_It end) {
  using value_type = decltype(*begin);
  impl::calc_something_impl<value_type>::calc(begin, end);
}


В calc_something вы так же можете поместить нужный вам код для разбирательства с тем, что на самом деле представляет из себя wchar_t.
Re[5]: ненависть к итераторам
От: Voivoid Россия  
Дата: 26.12.20 12:52
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Да придирки... просто насколько я понял std::iterator_traits не отличает константные итераторы от не константных. Как с std::iterator_traits сделать что-бы метод принимал итераторы/указатели только не константные, иначе говоря, что бы код выше не компилировался, а вот такой компилировался?


Тогда что-нибудь типа такого можно добавить:

template <typename Iter>
constexpr bool is_const_iter_v = std::is_const< std::remove_reference_t< std::iterator_traits< Iter >::reference > >::value;


Итоговый пример: https://ideone.com/UtLFvi
Re[5]: ненависть к итераторам
От: rg45 СССР  
Дата: 26.12.20 14:31
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:


V>Да придирки... просто насколько я понял std::iterator_traits не отличает константные итераторы от не константных. Как с std::iterator_traits сделать что-бы метод принимал итераторы/указатели только не константные, иначе говоря, что бы код выше не компилировался, а вот такой компилировался?


Константный и неконстантный итераторы имеют одинаковый value_type, но разные reference. Так что все в твоих руках. Вот так, например:

template <typename It>
using is_nonconst_iterator = std::is_same<
  typename std::iterator_traits<It>::reference, 
  typename std::iterator_traits<It>::value_type&>;


template<typename It, std::enable_if_t<is_nonconst_iterator<It>::value, int> = 0>
constexpr uint_t CalcSomething(It beg, It end);
--
Отредактировано 26.12.2020 14:48 rg45 . Предыдущая версия .
Re: ненависть к итераторам
От: sergii.p  
Дата: 27.12.20 11:31
Оценка:
Здравствуйте, Videoman, Вы писали:

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>


я обычно так и пишу. Но внутри делегирую вызов обычной шаблонной функции без ограничений (типа CalcSmthImpl). Получается извне функция ограничена по типам. А функция CalcSmthImpl обычно спрятана в cpp файле и её никто не видит. Можно такое же делать со специализацией, но ошибка при этом получается менее красивая
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 09:45
Оценка:
Здравствуйте, so5team, Вы писали:

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


Почему вы думаете что я не в состоянии решить возникающие передо мной задачи, кроме как обращаясь за советом на RSDN и делая copy-paste. Задача специально описана несколько абстрактно, т.к. меня интересовал общий подход на уровне С++17. Я просто хотел удостовериться что нигде не переусложняю код. Тем не менее, несколько человек не только поняли о чем речь, но и помогли без нравоучений и дали очень полезные и дельные советы.

S>Если так, то сходу напрашивается что-то такое:

S>
S>namespace impl
S>{

S>template<typename Basic_Type>
S>struct calc_something_impl;

S>template<>
S>struct calc_something_impl<char> {
S>  template<typename First_It, typename Last_It>
S>  static void calc(First_It begin, Last_It end) {...}
S>};

S>template<>
S>struct calc_something_impl<char16_t> {
S>  template<typename First_It, typename Last_It>
S>  static void calc(First_It begin, Last_It end) {...}
S>};

S>... // Тоже самое и для других нужных вам типов.
S>} /* namespace impl */

S>template<typename First_It, typename Last_It>
S>void calc_something(First_It begin, Last_It end) {
S>  using value_type = decltype(*begin);
S>  impl::calc_something_impl<value_type>::calc(begin, end);
S>}
S>


S>В calc_something вы так же можете поместить нужный вам код для разбирательства с тем, что на самом деле представляет из себя wchar_t.


Нет, так не пойдет:
1. У вас все ошибки будут происходить уже после разрешения перегрузки void calc_something(First_It begin, Last_It end) в ее потрохах. В исходных данных у меня было просто четыре функции, которые просто игнорируются если типы не подходят.
2. У вас не соблюдается const-корректность, функции не учитывают что аргументы должны обладать семантикой константных указателей.
3. Вы почему-то игнорируете то, что методы constexpr и абсолютно не помогли возникающей в связи с этим проблемой с конвертацией итераторов wchar_t в соответствующие типы.

P.S. На самом деле ответы на интересующие вопросы я получил. Еще раз убедился что современный С++ все еще не совершенен и ему есть куда стремиться.
Если кому интересно в итоге в общем виде решение у меня получилось такое:
    // Non-const iterator-like type check

    template<typename It, typename... Value> 
    struct is_iterator_of
    {
        using ItValue = typename std::iterator_traits<It>::value_type;
        using ItReference = typename std::iterator_traits<It>::reference;

        template<typename It>
        static constexpr bool is_non_const_v = std::is_same_v<
            std::remove_reference_t<ItReference>
            , std::remove_const_t<std::remove_reference_t<ItReference>>>;

        static constexpr bool value = std::disjunction_v<std::is_same<ItValue, Value>...> && is_non_const_v<It>;
    };

    template<typename It, typename... Value>
    constexpr bool is_iterator_of_v = is_iterator_of<It, Value...>::value;

    // Const iterator-like type check

    template<typename It, typename... Value>
    struct is_const_iterator_of
    {
        using ItValue = typename std::iterator_traits<It>::value_type;

        static constexpr bool value = std::disjunction_v<std::is_same<ItValue, Value>...>;
    };

    template<typename It, typename... Value>
    constexpr bool is_const_iterator_of_v = is_const_iterator_of<It, Value...>::value;



    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Standalone methods
    ////////////////////////////////////////////////////////////////////////////////////////////////

    template<typename It, std::enable_if_t<is_const_iterator_of_v<It, char>, int> = 0>
    constexpr It CalcSomething(It str, It last) noexcept;
    template<typename It, std::enable_if_t<is_const_iterator_of_v<It, char16_t>
        || (is_const_iterator_of_v<It, wchar_t> && sizeof(wchar_t) == sizeof(char16_t)), int> = 0>
    constexpr It CalcSomething(It str, It last) noexcept;
    template<typename It, std::enable_if_t<is_const_iterator_of_v<It, char32_t>
        || (is_const_iterator_of_v<It, wchar_t> && sizeof(wchar_t) == sizeof(char32_t)), int> = 0>
    constexpr It CalcSomething(It str, It last) noexcept;

В результате перегрузки просто игнорятся если типы не подходят, константность соблюдается, все работает на этапе компиляции без всяких преобразований типов.
Re: ненависть к итераторам
От: B0FEE664  
Дата: 28.12.20 10:19
Оценка:
Здравствуйте, 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>Проблема первая, как перегрузить итераторы только для перечисленных типов?

Смею предположить, что концепция STL и ваш подход — это в некотором смысле перпендикулярные подходы. В парадигме STL, как я её понимаю, вместо ваших CalcSomething(..) следует вызывать
std::for_each(itBegin, itEnd, fnFunctor) и передавать третьим параметром специализированный функтор. Смысл в том, чтобы отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге. Таким образом вам нужно перегружать не итераторы, а функторы. При этом сам функтор иногда (не всегда) имеет смысл разделить на две сущности: замыкание хранящие результат вычисления на каждом шаге и функцию подсчёта следующего значения. В результате должно получится нечто вроде:

uint_t result{};
std::for_each(itBegin, itEnd, [&result](char byte){ result = Calc(result, byte); });


(Начиная с С++11 std::for_each можно заменить на Range-based for loop, если проход совершается по всему контейнеру.)

Таким образом задача "перегрузить итераторы только для перечисленных типов" сводится к задаче перегрузке функции Calc(PreviousResult, NewValue).
И каждый день — без права на ошибку...
Re[2]: ненависть к итераторам
От: watchmaker  
Дата: 28.12.20 10:55
Оценка: +1
Здравствуйте, B0FEE664, Вы писали:

BFE>Смею предположить, что концепция STL и ваш подход — это в некотором смысле перпендикулярные подходы. В парадигме STL, как я её понимаю, вместо ваших CalcSomething(..) следует вызывать

BFE>std::for_each(itBegin, itEnd, fnFunctor) и передавать третьим параметром специализированный функтор. Смысл в том, чтобы отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге. Таким образом вам нужно перегружать не итераторы, а функторы.

Во-первых, мягко говоря, тут спорное утверждение про парадигму stl Откуда оно взялось?
Во-вторых, это не работает для сколько-нибудь интересного алгоритма, отличного от упомянутого однократного прохода с аккумулятором.

Можно сказать, что как раз неработоспособность такого подхода — это причина почему в C++ (да и в других языках) есть отдельные функции std::sort, std::nth_element и std::binary_search, но нет std::for_each(itBegin, itEnd, std::sort_functor{}), for_each(…, std::nth_element_functor{n}) или for_each(…, std::binary_search_functor{n}).



То есть, конечно, можно сделать сортировку или тот же nth_element через вызов вида std::for_each(itBegin, itEnd, some_algo_functor{}), если в функторе завести std::vector<value_type*> и на каждой итерации for_each делать push_back в него. И после накопления всех данных сделать уже реальную работу. Но это будет совсем не бесплатно. Да и ужасно плохо обобщается на случай только input_iterator (где только прямой проход), так как элемент может перестать существовать после it++ (то есть std::for_each ошибку не обнаружит, так как для него требований input_iterator достаточно).
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 10:55
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Смею предположить, что концепция STL и ваш подход — это в некотором смысле перпендикулярные подходы. В парадигме STL, как я её понимаю, вместо ваших CalcSomething(..) следует вызывать

BFE>std::for_each(itBegin, itEnd, fnFunctor) и передавать третьим параметром специализированный функтор. Смысл в том, чтобы отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге. Таким образом вам нужно перегружать не итераторы, а функторы. При этом сам функтор иногда (не всегда) имеет смысл разделить на две сущности: замыкание хранящие результат вычисления на каждом шаге и функцию подсчёта следующего значения. В результате должно получится нечто вроде:

BFE>
BFE>uint_t result{};
BFE>std::for_each(itBegin, itEnd, [&result](char byte){ result = Calc(result, byte); });
BFE>


BFE>(Начиная с С++11 std::for_each можно заменить на Range-based for loop, если проход совершается по всему контейнеру.)


BFE>Таким образом задача "перегрузить итераторы только для перечисленных типов" сводится к задаче перегрузке функции Calc(PreviousResult, NewValue).


Ох. Мысль интересная, но на сколько данный подход правильные и универсальный мне сложно судить. Давайте немного усложним код, допусти так:
constexpr const char* CalcSomething(const char* beg, const char* end) // ...
constexpr const char16_t* CalcSomething(const char16_t* beg, const char16_t* end) // ...
constexpr const char32_t* CalcSomething(const char32_t* beg, const char32_t* end) // ...
constexpr const wchar_t* CalcSomething(const wchar_t* beg, const wchar_t* end) // ...

т.е. у нас на выходе итератор зависит от алгоритма внутри метода. Что на это скажет STL и на сколько будет более читаемы код на выходе?

P.S. Мне не нравится как многие вещи в STL сделаны, просто приходится с этим жить. Вообще если посмотреть со стороны, то мой подход тоже вполне вписывается в функциональный стиль — функция "чистая", аргументы константные, принимает все что подходит. По сигнатуре не обязательно должно быть что там какой-то цикл от beg до end.
Re[3]: ненависть к итераторам
От: so5team https://stiffstream.com
Дата: 28.12.20 11:00
Оценка:
Здравствуйте, Videoman, Вы писали:

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


V>Почему вы думаете что я не в состоянии решить возникающие передо мной задачи, кроме как обращаясь за советом на RSDN и делая copy-paste.


Вот почему вы решили что я думаю именно так.

Вы задали вопрос на форуме. На меня ваш вопрос произвел впечатление, что вы хотели решить какую-то задачу и получили код, который вам не нравится. И спрашиваете как улучшить код. При этом стороннему читателю (т.е. мне конкретно) не понятно что за задачу вы решаете. О чем я вам и сказал. Мол, если бы вы озвучили именно задачу (пусть и абстрактно, но подробнее), то вам могли бы посоветовать совсем другие способы ее решения.

V>Я просто хотел удостовериться что нигде не переусложняю код.


Как по мне, так вы как раз простого кода и не получили.

V>Тем не менее, несколько человек не только поняли о чем речь, но и помогли без нравоучений и дали очень полезные и дельные советы.


Извините, что оказался не из их числа.

V>Нет, так не пойдет:


Нет проблем, хозяин барин.

V>1. У вас все ошибки будут происходить уже после разрешения перегрузки void calc_something(First_It begin, Last_It end) в ее потрохах. В исходных данных у меня было просто четыре функции, которые просто игнорируются если типы не подходят.


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

V>2. У вас не соблюдается const-корректность, функции не учитывают что аргументы должны обладать семантикой константных указателей.


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

V>3. Вы почему-то игнорируете то, что методы constexpr


А у вас не хватило фантазии, чтобы в предложенной мной схеме расставить constexpr по вкусу? Ну OK.

V>и абсолютно не помогли возникающей в связи с этим проблемой с конвертацией итераторов wchar_t в соответствующие типы.


А с этим есть проблема? Какая, если не секрет. Вопрос без сарказма из иронии.

V>Если кому интересно в итоге в общем виде решение у меня получилось такое:


Остается пожелать удачи тому, кто в этих портянках затем будет разбираться. И остается надеятся, что в С++20 от этого пердолинга можно будет избавится за счет концептов.
Re[4]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 11:14
Оценка:
Здравствуйте, so5team, Вы писали:

S>Как по мне, так вы как раз простого кода и не получили.


Во всяком случае я получил ответ на свой вопрос.

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


С таким подходом невозможно добавлять расширения методов для разных типов, т.к. такая "жадная" перегрузка будет цепляться ко всем типам без исключения.

S>Поскольку у вас не было нормального общего описания задачи, то лично я не понял, что же именно вам нужно. Вычитывать и свести воедино куски информации из всех веток обсуждения не вышло, уж проссыте.


Извиняюсь, следующий раз буду описывать подробнее.

V>>3. Вы почему-то игнорируете то, что методы constexpr


S>А у вас не хватило фантазии, чтобы в предложенной мной схеме расставить constexpr по вкусу? Ну OK.


Да не в этом дело.

V>>и абсолютно не помогли возникающей в связи с этим проблемой с конвертацией итераторов wchar_t в соответствующие типы.


S>А с этим есть проблема? Какая, если не секрет. Вопрос без сарказма из иронии.


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

V>>Если кому интересно в итоге в общем виде решение у меня получилось такое:


S>Остается пожелать удачи тому, кто в этих портянках затем будет разбираться. И остается надеятся, что в С++20 от этого пердолинга можно будет избавится за счет концептов.


А что делать. Да и не переживайте вы так, никто особо разбираться не будет. Это библиотечный код, остальные будут использовать и радоваться что получают вменяемые ошибки, а не простыни невразумительных ошибок от компилятора.
Re[5]: ненависть к итераторам
От: so5team https://stiffstream.com
Дата: 28.12.20 11:29
Оценка:
Здравствуйте, Videoman, Вы писали:

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


V>С таким подходом невозможно добавлять расширения методов для разных типов, т.к. такая "жадная" перегрузка будет цепляться ко всем типам без исключения.


Если у вас calc_something может работать не только с разными char-ами, но и с какими-нибудь int-ами или float-ами, но при этом она сохраняет и свое имя, и свой набор аргументов, то какая проблема поддерживать новые типы в impl::some_calc_something?

S>>Поскольку у вас не было нормального общего описания задачи, то лично я не понял, что же именно вам нужно. Вычитывать и свести воедино куски информации из всех веток обсуждения не вышло, уж проссыте.


V>Извиняюсь, следующий раз буду описывать подробнее.


Я вот так и не понял, если вы хотите, чтобы допускался такой код:
char * p = ...;
calc_something(p, p+3);

то почему должно быть запрещено вот такое?
char * p = ...;
const char * e = p+3;
calc_something(p, e);



V>>>и абсолютно не помогли возникающей в связи с этим проблемой с конвертацией итераторов wchar_t в соответствующие типы.


S>>А с этим есть проблема? Какая, если не секрет. Вопрос без сарказма из иронии.


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


Так что это за проблемы? wchar_t -- это не отдельный тип, а просто typedef для чего-то и компилятор не разделяет unsigned short и wchar_t? Или что-то другое?

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


Как бы намек на то, что когда компилятор говорит, что не может выбрать одну из N перегрузок, в каждой из которых стоит забористый enable_if_t, то пользователю от этого лучше не становится.
Re: ненависть к итераторам
От: XOOIOOX  
Дата: 28.12.20 16:15
Оценка:
Здравствуйте, Videoman, Вы писали:

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>


Я дико извиняюсь, но это же не STL'ные итераторы. Может, если использовать стандартные контейнеры и их итераторы, не придется городить огород?
Re[2]: ненависть к итераторам
От: watchmaker  
Дата: 28.12.20 16:35
Оценка: +2 :)
Здравствуйте, XOOIOOX, Вы писали:

XOO>Здравствуйте, Videoman, Вы писали:


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>>


XOO>Я дико извиняюсь, но это же не STL'ные итераторы.


Вот как раз STL про всех них говорит, что это самые настоящие итераторы, со всеми причитающимися им свойствами, например:
using test = std::iterator_traits<const char*>::value_type;

static_assert(
    std::is_same_v<
        std::iterator_traits<const char*>::iterator_category,
        std::random_access_iterator_tag
    >
);
Отредактировано 28.12.2020 17:32 watchmaker . Предыдущая версия . Еще …
Отредактировано 28.12.2020 16:37 watchmaker . Предыдущая версия .
Re[3]: ненависть к итераторам
От: B0FEE664  
Дата: 28.12.20 16:45
Оценка:
Здравствуйте, watchmaker, Вы писали:

BFE>>Смею предположить, что концепция STL и ваш подход — это в некотором смысле перпендикулярные подходы. В парадигме STL, как я её понимаю, вместо ваших CalcSomething(..) следует вызывать

BFE>>std::for_each(itBegin, itEnd, fnFunctor) и передавать третьим параметром специализированный функтор. Смысл в том, чтобы отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге. Таким образом вам нужно перегружать не итераторы, а функторы.

W>Во-первых, мягко говоря, тут спорное утверждение про парадигму stl Откуда оно взялось?

Вот так я понимаю парадигму stl, о чём и написано выше.

W>Во-вторых, это не работает для сколько-нибудь интересного алгоритма, отличного от упомянутого однократного прохода с аккумулятором.

Что ? Сортировка не работает для суммирования? Вроде же ясно написал "отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге". Вот у вас есть линейный проход: for_each(..), а есть нелинейный проход: std::binary_search. Есть операции изменения последовательности std::rotate и sort и пр., а есть операции изменения значений std::transform. Почти в каждом случае есть разделение операций над самой последовательностью и операций над данными последовательности. Почти, потому что есть такие функции как find и remove, которые используют прописанные операции сравнения прямо в алгоритме, но даже для них есть возможность переопределить эту операцию для объектов или же использовать вариант с суффиксом _if. Так что я совсем не понимаю ваших претензий.
И каждый день — без права на ошибку...
Re[6]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 17:21
Оценка:
S>Если у вас calc_something может работать не только с разными char-ами, но и с какими-нибудь int-ами или float-ами, но при этом она сохраняет и свое имя, и свой набор аргументов, то какая проблема поддерживать новые типы в impl::some_calc_something?

У меня такая специфика. Я пишу библиотеки, в том числе, и как их будут использовать мне не известно. На самом деле функций куча и они вызывают друг друга. Я предоставляю базовый функционал, если человек хочет "оптимизировать", например свою реализацию сделать, только отдельную функцию, то он только ее и предоставляет, причем в своем namespace. В вашем подходе, компиляция проваливается, по сути, внутрь библиотеки, и там куча всяких вызовов может быть, а уже там не срабатывает почему-то перегрузка. Внутри библиотеки уже может быть не так просто подставить свою реализацию. Короче — так намного гибче и расширяемее.

S>Я вот так и не понял, если вы хотите, чтобы допускался такой код:

S>
S>char * p = ...;
S>calc_something(p, p+3);
S>

S>то почему должно быть запрещено вот такое?
S>
S>char * p = ...;
S>const char * e = p+3;
S>calc_something(p, e);
S>


А кто сказал что такое запрещено. Запрещено такое:
const char* p = ...;
const char* e = p + 3;
modify_something(p, e);


S>Так что это за проблемы? wchar_t -- это не отдельный тип, а просто typedef для чего-то и компилятор не разделяет unsigned short и wchar_t? Или что-то другое?

wchar_t — это build-in тип, но в зависимости от платформы я хочу что бы он вызывал либо специализацию для char16_t, либо char32_t, что бы не повторять весь их функционал, но мало того что в constexpr методах запрещено преобразование одних указателей в другие, так еще и преобразование итераторов в общем виде не реализуемо в принципе. Вот мне и подсказали как выйти из данной ситуации.

S>Как бы намек на то, что когда компилятор говорит, что не может выбрать одну из N перегрузок, в каждой из которых стоит забористый enable_if_t, то пользователю от этого лучше не становится.


Что-то вы как-то категорично. У меня компилятор просто говорит что не может вызвать метод, т.к. не может найди подходящий с подходящими типами аргументов. Точно также как и в случае с простыми перегрузками функций. Причем он говорит какой именно метод ему нужен, а не где-то в потрохах библиотеки.
Re[2]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 17:25
Оценка:
Здравствуйте, XOOIOOX, Вы писали:

XOO>Я дико извиняюсь, но это же не STL'ные итераторы. Может, если использовать стандартные контейнеры и их итераторы, не придется городить огород?


Тогда что такое по вашему STL-ные итераторы? STL расширяемая библиотека и все что имеет интерфейс итератора им и является, например указатели.
Re[4]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 17:34
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Что ? Сортировка не работает для суммирования? Вроде же ясно написал "отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге". Вот у вас есть линейный проход: for_each(..), а есть нелинейный проход: std::binary_search. Есть операции изменения последовательности std::rotate и sort и пр., а есть операции изменения значений std::transform. Почти в каждом случае есть разделение операций над самой последовательностью и операций над данными последовательности. Почти, потому что есть такие функции как find и remove, которые используют прописанные операции сравнения прямо в алгоритме, но даже для них есть возможность переопределить эту операцию для объектов или же использовать вариант с суффиксом _if. Так что я совсем не понимаю ваших претензий.


На практике у меня функция собственно и инкапсулирует алгоритм прохода по итераторам, он не тривиальный и такого в STL быть не может. Функция не доходит до end итератора, она просто проверяет end что бы за конец буфера. Вот реальная функция, если что:
template<typename It, std::enable_if_t<is_const_iterator_of_v<It, char>, int> = 0>
constexpr It NextCodePoint(It str, It last) noexcept;

То-есть вы допускаете в STL существование своих функции вычисления значений и отказываете STL в возможности написания своих функций обхода?
Re[7]: ненависть к итераторам
От: so5team https://stiffstream.com
Дата: 28.12.20 18:04
Оценка:
Здравствуйте, Videoman, Вы писали:

V>У меня такая специфика. Я пишу библиотеки, в том числе, и как их будут использовать мне не известно. На самом деле функций куча и они вызывают друг друга. Я предоставляю базовый функционал, если человек хочет "оптимизировать", например свою реализацию сделать, только отдельную функцию, то он только ее и предоставляет, причем в своем namespace.


Что-то я упускаю, наверное. Но если ваш calc_something живет в вашем namespace, а собственные calc_something он делает в своем, то в чем вообще проблема? Тем более, что если пользовательский calc_something определяется для конкретных типов, то такая перегрузка будет выбрана вместо шаблонной версии.

Я бы еще понял, если бы вы позволяли пользователю переопределять calc_something в вашем namespace...

S>>то почему должно быть запрещено вот такое?

S>>
S>>char * p = ...;
S>>const char * e = p+3;
S>>calc_something(p, e);
S>>


V>А кто сказал что такое запрещено.


Так ведь здесь аргументы двух разных типов: char* и const char*, а у вас оба аргумента принадлежат одному типу It.
Re[8]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 18:11
Оценка:
Здравствуйте, so5team, Вы писали:

S>Что-то я упускаю, наверное. Но если ваш calc_something живет в вашем namespace, а собственные calc_something он делает в своем, то в чем вообще проблема? Тем более, что если пользовательский calc_something определяется для конкретных типов, то такая перегрузка будет выбрана вместо шаблонной версии.


S>Я бы еще понял, если бы вы позволяли пользователю переопределять calc_something в вашем namespace...


Ну так ADL же.

S>Так ведь здесь аргументы двух разных типов: char* и const char*, а у вас оба аргумента принадлежат одному типу It.


Ну так у функции calc_something(const char* beg, const char* end) тоже оба типа одинаковых, но вы же туда можете передавать char* и const char* в любых комбинациях.
Re[9]: ненависть к итераторам
От: so5team https://stiffstream.com
Дата: 28.12.20 18:37
Оценка:
Здравствуйте, Videoman, Вы писали:

S>>Что-то я упускаю, наверное. Но если ваш calc_something живет в вашем namespace, а собственные calc_something он делает в своем, то в чем вообще проблема? Тем более, что если пользовательский calc_something определяется для конкретных типов, то такая перегрузка будет выбрана вместо шаблонной версии.


S>>Я бы еще понял, если бы вы позволяли пользователю переопределять calc_something в вашем namespace...


V>Ну так ADL же.


И при чем здесь ADL?

S>>Так ведь здесь аргументы двух разных типов: char* и const char*, а у вас оба аргумента принадлежат одному типу It.


V>Ну так у функции calc_something(const char* beg, const char* end) тоже оба типа одинаковых, но вы же туда можете передавать char* и const char* в любых комбинациях.


Такое ощущение, что вы не пробовали: https://wandbox.org/permlink/KG5xwKFUi56i65vd
Re[10]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 19:12
Оценка:
Здравствуйте, so5team, Вы писали:

Вы мне предлагали фантазировать на тему constexpr, а сами не хотите фантазировать, тем более что ваш код не компилируется уже по причине того что нельзя char * p = "123456789";. Честно, я уже потерял нить того, что вы мне доказываете. Спор ради спора? У меня и так есть чем занять свое время.
Re[5]: ненависть к итераторам
От: B0FEE664  
Дата: 28.12.20 19:16
Оценка:
Здравствуйте, Videoman, Вы писали:

V>То-есть вы допускаете в STL существование своих функции вычисления значений и отказываете STL в возможности написания своих функций обхода?

Я не сказу за всю современную библиотеку С++20 с std::ranges, но в целом — да. Однако, обычно набора функций std:: хватает для решения задач.

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


Это смотря что понимать под "не тривиальный". Если у вас цикл или набор циклов по последовательности, то скорее всего можно обойтись функциями из std::
А вот если у вас такой алгоритм, где необходимы обратные смещения по последовательности и число обратных смещений не фиксировано, то тогда на может быть нет смысла использовать STL
И каждый день — без права на ошибку...
Re[11]: ненависть к итераторам
От: so5team https://stiffstream.com
Дата: 28.12.20 19:48
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Вы мне предлагали фантазировать на тему constexpr, а сами не хотите фантазировать


Мне не нужно фантазировать, это вы показали, к какому коду в итоге пришли:
template<typename It, std::enable_if_t<is_const_iterator_of_v<It, char>, int> = 0>
    constexpr It CalcSomething(It str, It last) noexcept;
    template<typename It, std::enable_if_t<is_const_iterator_of_v<It, char16_t>
        || (is_const_iterator_of_v<It, wchar_t> && sizeof(wchar_t) == sizeof(char16_t)), int> = 0>
    constexpr It CalcSomething(It str, It last) noexcept;
    template<typename It, std::enable_if_t<is_const_iterator_of_v<It, char32_t>
        || (is_const_iterator_of_v<It, wchar_t> && sizeof(wchar_t) == sizeof(char32_t)), int> = 0>
    constexpr It CalcSomething(It str, It last) noexcept;

Здесь один тип итератора. И если в итоге вы сделали вариант с двумя типами итераторов, значит мой пример был приведен не зря.

V>При этом ранее вы заостряли внимание на том, что тем более что ваш код не компилируется уже по причине того что нельзя char * p = "123456789";.


Это не ошибка, а предупреждение. Для иллюстрационного примера вполне простительно.
Re: ненависть к итераторам
От: vopl Россия  
Дата: 28.12.20 19:50
Оценка:
Здравствуйте, Videoman, Вы писали:

V>- преобразовать тип итераторов — я не могу


#include <type_traits>
#include <utility>
#include <numeric>
#include <boost/iterator_adaptors.hpp>

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
template <class SrcIter, class DstValue>
class IterAdaptor
  : public boost::iterator_adaptor<
        IterAdaptor<SrcIter, DstValue>      // Derived
      , SrcIter                             // Base
      , DstValue                            // Value
    >
{
 public:
    explicit IterAdaptor(const IterAdaptor::iterator_adaptor_::base_type& p)
      : IterAdaptor::iterator_adaptor_(p) {}

    DstValue& dereference() const
    {
        DstValue *ptr = reinterpret_cast<DstValue *>(&*this->base_reference());
        return *ptr;
    }
};

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
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;

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
template<typename It, std::enable_if_t<const_iterator_of_type_v<It, char>, int> = 0>
int CalcSomething(It beg, It end)
{
    return 100 + std::accumulate(beg, end, int{});
}

template<typename It, std::enable_if_t<const_iterator_of_type_v<It, char16_t>, int> = 0>
int CalcSomething(It beg, It end)
{
    return 200 + std::accumulate(beg, end, int{});
}

template<typename It, std::enable_if_t<const_iterator_of_type_v<It, char32_t>, int> = 0>
int CalcSomething(It beg, It end)
{
    return 300 + std::accumulate(beg, end, int{});
}

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

int main()
{
    wchar_t wchars[7];

    return CalcSomething(std::begin(wchars), std::end(wchars));
}
Re[12]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 28.12.20 20:26
Оценка:
Здравствуйте, so5team, Вы писали:

S>Здесь один тип итератора. И если в итоге вы сделали вариант с двумя типами итераторов, значит мой пример был приведен не зря.


У меня в коде именно что один тип итератора . Ваш вариант не скомпилируется, просто у меня нет таких экзотических случаев нигде в коде. Если будет необходимо, я могу разделить типы, это ни на что не повлияет. Еще раз, я-то как раз не оправдываю сложность и объем кода которые в итоге получились, но еще раз, посмотрите повнимательнее на название темы. Весь мой вопрос заключался в одном: неужели для того что бы сделать полностью эквивалентный изначальному код на итераторах нужно так извращаться? Ответ: да!
Re[13]: ненависть к итераторам
От: rg45 СССР  
Дата: 28.12.20 20:43
Оценка: +2
Здравствуйте, Videoman, Вы писали:

V>Весь мой вопрос заключался в одном: неужели для того что бы сделать полностью эквивалентный изначальному код на итераторах нужно так извращаться? Ответ: да!


При этом не стоит упускать из виду, что наиболее удачный рефаторинг — это не обязательно полностью эквивалентный код
--
Re[13]: ненависть к итераторам
От: so5team https://stiffstream.com
Дата: 29.12.20 06:24
Оценка:
Здравствуйте, Videoman, Вы писали:

S>>Здесь один тип итератора. И если в итоге вы сделали вариант с двумя типами итераторов, значит мой пример был приведен не зря.


V>У меня в коде именно что один тип итератора . Ваш вариант не скомпилируется, просто у меня нет таких экзотических случаев нигде в коде.


В C++ном range-for изначально тоже предполагалось, что у begin/end будет одинаковый тип. Но затем в C++17 пришлось вносить изменения.

V>Если будет необходимо, я могу разделить типы, это ни на что не повлияет.


Разве что на объем и сложность кода, т.к. вам придется вписывать дополнительные проверки.

V>Весь мой вопрос заключался в одном: неужели для того что бы сделать полностью эквивалентный изначальному код на итераторах нужно так извращаться? Ответ: да!


Есть замечательное изречение: "Вы дали ему то, что он просил. А я дал ему то, что ему было нужно". Вы явно ждали лишь того, о чем просили.

Если у вас появилось впечатление, что я пытаюсь в этом разговоре как-то унизить вас или продемонстрировать какое-то превосходство, то уверяю, это не так.

Дело в том, что я так же разрабатываю библиотеки, которые затем разные люди используют в разных условиях и для разных задач. Но исходя из своего опыта я не смог понять под какие сценарии вы затачиваете свой условный calc_something. В частности, от меня ускользают сценарии, в которых пользователь вашей библиотеки будет иметь возможность делать собственные перегрузки ваших функций и почему это может помешать шаблонным функциям в ваших пространствах имен. Отсюда и вопросы. Жаль, что тема использования ADL не получила своего развития. Я так и не понял, как взаимосвязаны разные реализации calc_something в разных пространствах имен с ADL.
Re[14]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 29.12.20 08:41
Оценка:
Здравствуйте, so5team, Вы писали:

S>В C++ном range-for изначально тоже предполагалось, что у begin/end будет одинаковый тип. Но затем в C++17 пришлось вносить изменения.


Да, про это я в курсе. Все готовятся к range-ам. Будем решать проблемы по мере их поступления. Пока мы плотно сидим старом С++17. На самом деле не известно насколько все это будет удобно и сколько кода придется писать и как быстро все это будет работать, когда народ массово кинется на 20 стандарт. Посмотрим.

S>Если у вас появилось впечатление, что я пытаюсь в этом разговоре как-то унизить вас или продемонстрировать какое-то превосходство, то уверяю, это не так.


S>Дело в том, что я так же разрабатываю библиотеки, которые затем разные люди используют в разных условиях и для разных задач. Но исходя из своего опыта я не смог понять под какие сценарии вы затачиваете свой условный calc_something. В частности, от меня ускользают сценарии, в которых пользователь вашей библиотеки будет иметь возможность делать собственные перегрузки ваших функций и почему это может помешать шаблонным функциям в ваших пространствах имен. Отсюда и вопросы. Жаль, что тема использования ADL не получила своего развития. Я так и не понял, как взаимосвязаны разные реализации calc_something в разных пространствах имен с ADL.


На самом деле я действительно получил исчерпывающее объяснение на изначальный вопрос и дальнейшее обсуждение выльется в то, что мне просто придется выкладывать кучу кода из библиотеки. Дальше мы начнем обсуждать дальнейшие нюансы и т.д. и т.п. С++ можно обсуждать бесконечно, но к сожалению сейчас просто нет на это времени.
Re[2]: ненависть к итераторам
От: vopl Россия  
Дата: 29.12.20 09:54
Оценка:
Здравствуйте, vopl, Вы писали:

V>Здравствуйте, Videoman, Вы писали:


V>>- преобразовать тип итераторов — я не могу


для компайл тайм это тоже работает, https://gcc.godbolt.org/z/8nnWfE
Re[3]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 29.12.20 21:04
Оценка:
Здравствуйте, vopl, Вы писали:

V>Здравствуйте, vopl, Вы писали:


V>>Здравствуйте, Videoman, Вы писали:


V>>>- преобразовать тип итераторов — я не могу


V>для компайл тайм это тоже работает, https://gcc.godbolt.org/z/8nnWfE


Идею я понял, но для меня это слишком креативно . И к сожалению на С++17 не работает, reinterpret_cast там никак.
Re[4]: ненависть к итераторам
От: vopl Россия  
Дата: 30.12.20 07:03
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

V> к сожалению на С++17 не работает

работает
https://gcc.godbolt.org/z/d8so4n

V> reinterpret_cast там никак.

это мелочи, я его туда вкорячил потому что было лень думать. Вместо него можно другие средства запользовать. Например, можно вообще ссылку не предоставлять из operator*, а сразу значение (если тебя это устроит, мне со стороны не видно) — тогда не нужно будет спускаться на уровень указателей, следовательно, никаких ХХХ_cast для указателей вообще. Это лишь пример, не более.
Re[5]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 30.12.20 09:12
Оценка:
Здравствуйте, vopl, Вы писали:

V>это мелочи, я его туда вкорячил потому что было лень думать. Вместо него можно другие средства запользовать. Например, можно вообще ссылку не предоставлять из operator*, а сразу значение (если тебя это устроит, мне со стороны не видно) — тогда не нужно будет спускаться на уровень указателей, следовательно, никаких ХХХ_cast для указателей вообще. Это лишь пример, не более.


Идея с адаптером супер. Буду иметь в виду если прижмет где-то. В продакшене я боюсь такое использовать пока
Re[2]: ненависть к итераторам
От: _NN_ www.nemerleweb.com
Дата: 30.12.20 10:41
Оценка:
Здравствуйте, vopl, Вы писали:

Небольшое улучшение с учётом C++20.
Кстати а нужен ли здесь boost вообще ?

#include <type_traits>
#include <utility>
#include <numeric>

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
template <class SrcIter, class DstValue>
class IterAdaptor
{
public:
    using iterator_category = typename std::iterator_traits<SrcIter>::iterator_category;
    using iterator_concept = typename std::iterator_traits<SrcIter>::iterator_concept;
    using value_type = DstValue;
    using difference_type = std::iter_difference_t<SrcIter>;
    using pointer = std::add_pointer_t<DstValue>;
    using reference = std::add_lvalue_reference_t<DstValue>;

    SrcIter iter;

    constexpr IterAdaptor(SrcIter iter):iter(iter){}

    auto operator<=>(IterAdaptor const& other) const = default;

    constexpr DstValue operator*() { return *iter; }// со ссылками не заморачиваюсь пока, но судя по всему это нужно будет сделать
    constexpr IterAdaptor& operator++() { ++iter; return *this; }
};
/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7

template<typename It, typename Value>
concept const_iterable_of_type = std::same_as<std::decay_t<decltype(*std::declval<It>())>, Value>;

using wchar_t_as_char_t =
    std::conditional_t<sizeof(wchar_t) == sizeof(char16_t), char16_t,
    std::conditional_t < sizeof(wchar_t) == sizeof(char32_t), char32_t, void>>;
static_assert(!std::is_same_v<wchar_t_as_char_t, void>);

/////////0/////////1/////////2/////////3/////////4/////////5/////////6/////////7
template<const_iterable_of_type<char> It>
constexpr int CalcSomething(It beg, It end)
{
    return 100 + std::accumulate(beg, end, int{});
}

template<const_iterable_of_type<char16_t> It>
constexpr int CalcSomething(It beg, It end)
{
return 200 + std::accumulate(beg, end, int{});
}

template<const_iterable_of_type<char32_t> It>
constexpr int CalcSomething(It beg, It end)
{
    return 300 + std::accumulate(beg, end, int{});
}

template<const_iterable_of_type<wchar_t> It>
constexpr int CalcSomething(It beg, It end)
{
    using Adaptor = IterAdaptor<It, const wchar_t_as_char_t>;
    return CalcSomething(Adaptor{ beg }, Adaptor{ end }); // ????
}

int main()
{
    constexpr wchar_t wchars[7] = L"abc";

    constexpr auto x = CalcSomething(std::begin(wchars), std::end(wchars));
    return x;
}
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: ненависть к итераторам
От: Videoman Россия https://hts.tv/
Дата: 30.12.20 12:09
Оценка:
Здравствуйте, _NN_, Вы писали:

А вот интересный у меня вопрос, так как я с концептами в живую еще не сталкивался: вот это вот:
template<const_iterable_of_type<wchar_t> It>
constexpr int CalcSomething(It beg, It end)

Будет разрешаться когда оба итератора строго одного типа или, в отличие от варианта на С++, будет позволять любые типы подходящие под concept ? Сдается мне что будет выведен какой-то один тип и придется также дублировать:
template<const_iterable_of_type<wchar_t> ItBegin, const_iterable_of_type<wchar_t> ItEnd>

Что говорят концепты? Это можно будет на них как-то упростить?
Re[4]: ненависть к итераторам
От: watchmaker  
Дата: 30.12.20 13:41
Оценка:
Здравствуйте, Videoman, Вы писали:


V>А вот интересный у меня вопрос, так как я с концептами в живую еще не сталкивался: вот это вот:

V>
V>template<const_iterable_of_type<wchar_t> It>
V>constexpr int CalcSomething(It beg, It end)
V>

V>Будет разрешаться когда оба итератора строго одного типа или, в отличие от варианта на С++, будет позволять любые типы подходящие под concept ?
В смысле, чтобы работал следующий вызов?
CalcSomething(std::list<wchar_t>::const_iterator{}, std::vector<wchar_t>::const_iterator{});
Тогда если в теле функции CalcSomething будет написано It a;, то какой тип из двух должна получить эта переменная a?


V>Сдается мне что будет выведен какой-то один тип

Да. Конструкция
template<Concept T> 
auto foo(T param) …
эквивалентна
template<typename T>
requires Concept<T>
auto foo(T param) …

Из этого должно быть понятно как ограничения работают в случае многих параметров.

V>Что говорят концепты? Это можно будет на них как-то упростить?

Если тебе не нужно что-то дополнительное от типа, то можно просто не использовать полную запись: выкинуть template и использовать concept auto для аргументов. Работает так же как и в остальных местах: каждый auto выводится независимо.
Re[5]: ненависть к итераторам
От: _NN_ www.nemerleweb.com
Дата: 30.12.20 13:59
Оценка:
Здравствуйте, watchmaker, Вы писали:


V>>Сдается мне что будет выведен какой-то один тип

W>Да. Конструкция
template<Concept T> 
W>auto foo(T param) …
W>
эквивалентна
template<typename T>
W>requires Concept<T>
W>auto foo(T param) …
W>

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

Кстати, хорошо упомянуть, что в случае более одного аргумента компилятор ставить тип в начало, что может удивить

template<const_iterable_of_type<char> It>
constexpr int CalcSomething(It beg, It end);

==
template<typename It> requires const_iterable_of_type<It, char>
constexpr int CalcSomething(It beg, It end);
http://rsdn.nemerleweb.com
http://nemerleweb.com
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.