Не понятна сама задача, которую вы пытаетесь решить. Вместо того, чтобы ее описать так, чтобы она была понятна сторонним людям, вы выкатили кучу кода, который иллюстрирует лишь ту попытку, которая вам не нравится. Что отвлекает от поиска решения исходной задачи, провоцируя поиск исправления вашей попытки. Может быть, если вы сформулируете то, что вам хочется, то вам быстрее подскажут в какую сторону двигаться.
Сложилось впечатление, что у вас были обычные перегрузки для нескольких типов. Которые неявно подразумевали, что [begin, end) -- это непрерывный блок данных. Теперь вы захотели сделать свои шаблонные функции, которые бы принимали в качестве begin и end итераторы, дабы можно было работать с произвольными последовательностями, необязательно непрерывными.
Здравствуйте, Videoman, Вы писали:
V>Да придирки... просто насколько я понял std::iterator_traits не отличает константные итераторы от не константных. Как с std::iterator_traits сделать что-бы метод принимал итераторы/указатели только не константные, иначе говоря, что бы код выше не компилировался, а вот такой компилировался?
V>Да придирки... просто насколько я понял std::iterator_traits не отличает константные итераторы от не константных. Как с std::iterator_traits сделать что-бы метод принимал итераторы/указатели только не константные, иначе говоря, что бы код выше не компилировался, а вот такой компилировался?
Константный и неконстантный итераторы имеют одинаковый value_type, но разные reference. Так что все в твоих руках. Вот так, например:
я обычно так и пишу. Но внутри делегирую вызов обычной шаблонной функции без ограничений (типа CalcSmthImpl). Получается извне функция ограничена по типам. А функция CalcSmthImpl обычно спрятана в cpp файле и её никто не видит. Можно такое же делать со специализацией, но ошибка при этом получается менее красивая
Здравствуйте, so5team, Вы писали:
S>Не понятна сама задача, которую вы пытаетесь решить. Вместо того, чтобы ее описать так, чтобы она была понятна сторонним людям, вы выкатили кучу кода, который иллюстрирует лишь ту попытку, которая вам не нравится. Что отвлекает от поиска решения исходной задачи, провоцируя поиск исправления вашей попытки. Может быть, если вы сформулируете то, что вам хочется, то вам быстрее подскажут в какую сторону двигаться.
Почему вы думаете что я не в состоянии решить возникающие передо мной задачи, кроме как обращаясь за советом на RSDN и делая copy-paste. Задача специально описана несколько абстрактно, т.к. меня интересовал общий подход на уровне С++17. Я просто хотел удостовериться что нигде не переусложняю код. Тем не менее, несколько человек не только поняли о чем речь, но и помогли без нравоучений и дали очень полезные и дельные советы.
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. На самом деле ответы на интересующие вопросы я получил. Еще раз убедился что современный С++ все еще не совершенен и ему есть куда стремиться.
Если кому интересно в итоге в общем виде решение у меня получилось такое:
В результате перегрузки просто игнорятся если типы не подходят, константность соблюдается, все работает на этапе компиляции без всяких преобразований типов.
Здравствуйте, Videoman, Вы писали:
V>За что я "люблю" эту концепцию STL так это за то, что наступаешь на грабли в самых казалось бы простых вещах! V>Захотел написать обобщенный код, было: V>
V>Все отлично работает, был простой код. Не совсем гибко, а давайте еще обобщим и добавим что бы работало с любыми итераторами. V>Проблема первая, как перегрузить итераторы только для перечисленных типов?
Смею предположить, что концепция STL и ваш подход — это в некотором смысле перпендикулярные подходы. В парадигме STL, как я её понимаю, вместо ваших CalcSomething(..) следует вызывать
std::for_each(itBegin, itEnd, fnFunctor) и передавать третьим параметром специализированный функтор. Смысл в том, чтобы отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге. Таким образом вам нужно перегружать не итераторы, а функторы. При этом сам функтор иногда (не всегда) имеет смысл разделить на две сущности: замыкание хранящие результат вычисления на каждом шаге и функцию подсчёта следующего значения. В результате должно получится нечто вроде:
Здравствуйте, 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 достаточно).
Здравствуйте, B0FEE664, Вы писали:
BFE>Смею предположить, что концепция STL и ваш подход — это в некотором смысле перпендикулярные подходы. В парадигме STL, как я её понимаю, вместо ваших CalcSomething(..) следует вызывать BFE>std::for_each(itBegin, itEnd, fnFunctor) и передавать третьим параметром специализированный функтор. Смысл в том, чтобы отделить алгоритм прохода по итераторам от алгоритма вычисления значения на каждом шаге. Таким образом вам нужно перегружать не итераторы, а функторы. При этом сам функтор иногда (не всегда) имеет смысл разделить на две сущности: замыкание хранящие результат вычисления на каждом шаге и функцию подсчёта следующего значения. В результате должно получится нечто вроде:
BFE>
BFE>(Начиная с С++11 std::for_each можно заменить на Range-based for loop, если проход совершается по всему контейнеру.)
BFE>Таким образом задача "перегрузить итераторы только для перечисленных типов" сводится к задаче перегрузке функции Calc(PreviousResult, NewValue).
Ох. Мысль интересная, но на сколько данный подход правильные и универсальный мне сложно судить. Давайте немного усложним код, допусти так:
т.е. у нас на выходе итератор зависит от алгоритма внутри метода. Что на это скажет STL и на сколько будет более читаемы код на выходе?
P.S. Мне не нравится как многие вещи в STL сделаны, просто приходится с этим жить. Вообще если посмотреть со стороны, то мой подход тоже вполне вписывается в функциональный стиль — функция "чистая", аргументы константные, принимает все что подходит. По сигнатуре не обязательно должно быть что там какой-то цикл от beg до end.
Здравствуйте, 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 от этого пердолинга можно будет избавится за счет концептов.
Здравствуйте, so5team, Вы писали:
S>Как по мне, так вы как раз простого кода и не получили.
Во всяком случае я получил ответ на свой вопрос.
S>Не факт, что это лучше. Потому что зачастую разбираться с тем, почему компилятор не смог выбрать конкретную перегрузку, гораздо сложнее, чем увидеть уведомление об ошибке о том, что реализации для конкретного типа нет. Говорю как человек, у которого в сообщениях об ошибках компилятора имена типов занимают по несколько экранов.
С таким подходом невозможно добавлять расширения методов для разных типов, т.к. такая "жадная" перегрузка будет цепляться ко всем типам без исключения.
S>Поскольку у вас не было нормального общего описания задачи, то лично я не понял, что же именно вам нужно. Вычитывать и свести воедино куски информации из всех веток обсуждения не вышло, уж проссыте.
Извиняюсь, следующий раз буду описывать подробнее.
V>>3. Вы почему-то игнорируете то, что методы constexpr
S>А у вас не хватило фантазии, чтобы в предложенной мной схеме расставить constexpr по вкусу? Ну OK.
Да не в этом дело.
V>>и абсолютно не помогли возникающей в связи с этим проблемой с конвертацией итераторов wchar_t в соответствующие типы.
S>А с этим есть проблема? Какая, если не секрет. Вопрос без сарказма из иронии.
Проблема в том, что бы с одной стороны не дублировать код, а с другой стороны не нарваться на преобразование итераторов в указатели или указателей в указатели другого типа, которое в constexpr методах запрещено.
V>>Если кому интересно в итоге в общем виде решение у меня получилось такое:
S>Остается пожелать удачи тому, кто в этих портянках затем будет разбираться. И остается надеятся, что в С++20 от этого пердолинга можно будет избавится за счет концептов.
А что делать. Да и не переживайте вы так, никто особо разбираться не будет. Это библиотечный код, остальные будут использовать и радоваться что получают вменяемые ошибки, а не простыни невразумительных ошибок от компилятора.
Здравствуйте, 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, то пользователю от этого лучше не становится.
Здравствуйте, 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. Так что я совсем не понимаю ваших претензий.
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, то пользователю от этого лучше не становится.
Что-то вы как-то категорично. У меня компилятор просто говорит что не может вызвать метод, т.к. не может найди подходящий с подходящими типами аргументов. Точно также как и в случае с простыми перегрузками функций. Причем он говорит какой именно метод ему нужен, а не где-то в потрохах библиотеки.
Здравствуйте, XOOIOOX, Вы писали:
XOO>Я дико извиняюсь, но это же не STL'ные итераторы. Может, если использовать стандартные контейнеры и их итераторы, не придется городить огород?
Тогда что такое по вашему STL-ные итераторы? STL расширяемая библиотека и все что имеет интерфейс итератора им и является, например указатели.
Здравствуйте, 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 в возможности написания своих функций обхода?
Здравствуйте, 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.
Здравствуйте, 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* в любых комбинациях.
Здравствуйте, 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* в любых комбинациях.