Как стать автором
Обновить

Комментарии 36

1) Не хватает в сравнении хоть каких-то замеров по скорости работы и скорости компиляции. Причем особенно интересует режим отладки.


2) Как себя ведут рэнджи относительно исключений? Бросают ли сами? как реагируют на то, если итератор бросит?


3) Хотелось бы еще пример того, что будет показывать компилятор при опечатке и имени ренджовой функции. Боюсь что-то очень страшное.


4) Работают ли в constexpr контексте?

НЛО прилетело и опубликовало эту надпись здесь
Современные плюсы очень полагаются на оптимизатор.

современные плюсы (если говорить именно о коде стандартной библиотеки) сильно полагаются на одну оптимизацию — инлайнинг. Которую для полноценной отладки необходимо отключать. Концепты могут упростить код стандартной библиотеки, особенно в местах, где используется много sfinae, прокси-методов и сложные иерархии наследования. В ranges, однако, достаточно сильная вложенность by design. Но то, что они плохо подходят для отладочных билдов, не значит, что их не стоило принимать.

На мой взгляд, компиляторам просто нужен флажок (по умолчанию) для инлайнинга функций/методов из стандартной библиотеки, даже в дебаг режиме. Ведь с практической точки зрения, баг практически всегда не в них
Ну pragma optimize в MSVC есть, и он худо-бедо-костыльно эту проблему решает. Я для кода, который требовал тяжелых хидеров, для дебага делал так — для дебаг конфигурации форсился флаг /O2, а после подключения всех заголовков, для своего уже кода вставлялась pragma. Да, многословно и костыльно, но по итогу делало то что нужно.
НЛО прилетело и опубликовало эту надпись здесь
Конечно, инлайнинг не даст вам полноценный стек (а последующие оптимизации не дадут значения переменных, будет одно сплошное optimized out), но конструктивно отлаживаться всё равно можно. По моему опыту, опять же, по крайней мере.

ну это сильно зависит от того что вы понимаете под «полноценной отладкой», о которой я вел речь. Иногда для локализации бага достаточно знать имя функции с ошибкой. А иногда хочется погулять по брейкпоинтам, отследить значения переменных, и т.д. Хотя стек вызовов может быть сильно зашумлен вложенными функциями.

А лямбды, которые вы туда передаёте, инлайнить и иначе оптимизировать надо?

Пользовательские лямбды — нет, очевидно.
А если серьезно, есть впечатление, что комитет давно забил на скорость работы в сборках без оптимизаций.

С++ до «забил» ещё очень далеко. Недавно кто-то пытался дождаться выполнения дебажной сборки раста — не дождался.

сборках без оптимизаций.

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

Отладка подобной сборки не всегда возможна и зачастую попросту вредна. Не все программы могут работать с подобных замедлением + многопоточна среда(и не обязательно эти потоки уровня ОС).

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

В ситуации же с более мощной и универсальной отладкой трейсом — таких проблем нет. Как и нет проблем с отладкой подобных пайпов. Без проблем пишется операция «трейс» и вставляет куда угодно в пайп. А трейс — это просто подвид тестирования.

Из этого следует то, что подобные решения(даже если они имеют упомянутые вами проблемы) не приводят к проблемам отладки. Они приводят к проблемам лишь в одной методике отладки. А проблемы одного из подходов не являются проблемой в целом, а являются проблемой локальной для подхода.

Недавно кто-то пытался дождаться выполнения дебажной сборки раста — не дождался.

Где почитать?

Где почитать?

Везде. Тема поднималась тысячи раз. Я ссылался на это Где почитать — не знаю, меня эта тема не интересует.

Тьфу, я уж думал правда сам раст в дебаге собрать не могут.

НЛО прилетело и опубликовало эту надпись здесь
Ну, опять же, это, на мой сугубо личный и эмпирический взгляд, не всегда настолько уж полезно, как я писал в соседнем комментарии. -O2 -g вполне достаточно.

Это не моя мотивация и я ничего не знаю об этом. Для меня так же все минусы -O0 перекрывают плюсы.

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

Смотря как написать трейс.

Ошибки выражения логики на языке лучше отлаживать asan'ом, tsan'ом и тому подобным. Ошибки самой логики — тесты там всякие.

Трейс является источником данных. Интерпретировать их может не только человек, а интерпретация человек уже по-сути тестирование.

НЛО прилетело и опубликовало эту надпись здесь
Даже как memcpy в заmmapленный файл.

Трупут memcpy десятки гагабайт на ведро. Трупут современной файловой подсистемы не далеко от этого ушел. 99% программ даже сотен мегабайт трейса не генерируют. Такое кол-во программ даже близко не являются оптимальными, причём эта неоптимальность не исчисляется процентами, а исчисляется разами.

И что самое интересное, эти рассуждения вообще не имеют смысла. Если для отладки чего-то необходим даже мегабайт трейса — отладить это вручную попросту невозможно. Здесь нет какого-либо выбора «трейс или не трейс» и какие-то претензии к трейсу попросту нестоятельны.

Не хватает в сравнении хоть каких-то замеров по скорости работы

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

скорости компиляции

Текущая реализация очень тормозная, в том числе и по причине эмуляции концептов. Но реализация и не претендует на оптимальность.

2) Как себя ведут рэнджи относительно исключений? Бросают ли сами? как реагируют на то, если итератор бросит?

А что именно они там бросать должны и на что реагировать? Реагируют так же как и какой-нибудь std::transform()|range based for.

3) Хотелось бы еще пример того, что будет показывать компилятор при опечатке и имени ренджовой функции. Боюсь что-то очень страшное.


main.cpp:14:51: ошибка: «splite» is not a member of «rv»; did you mean «split»?
   14 |   auto count = rs::distance(rv::c_str(text) | rv::splite(' '));
      |                                                   ^~~~~~
      |                                                   split


main.cpp:17:62: ошибка: no match for «operator|» (operand types are «ranges::split_view<ranges::delimit_view<ranges::subrange<const char*, ranges::unreachable_sentinel_t, ranges::subrange_kind::unsized>, char>, ranges::single_view<char> >» and «int»)
   17 |   auto count = rs::distance(rv::c_str(text) | rv::split(' ') | x);
      |                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~
      |                                             |                  |
      |                                             |                  int
      |                                             ranges::split_view<ranges::delimit_view<ranges::subrange<const char*, ranges::unreachable_sentinel_t, ranges::subrange_kind::unsized>, char>, ranges::single_view<char> >


Вот пример: godbolt.org/z/VSykII

4) Работают ли в constexpr контексте?

Смотря что. Много работает, а много нет. В стандарте, думаю, будет работать всё.

Удалите два наименьших и два самых больших значения диапазона и оставьте остальные, упорядоченные во втором диапазоне.

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

Выведите числа от 101 до 200:

не уверен что такую задачу решать через range лучше

Объединить все строки в данном диапазоне в одно значение.

лишний rs::move справа

Подсчитайте количество слов (разделенных пробелом) в тексте.

если не обрабатывать ошибки, то проще вернуть std::count(..., ' ') + 1. А вообще используйте isspace, хорошая штука
Удалите два наименьших и два самых больших значения диапазона и оставьте остальные, упорядоченные во втором диапазоне.
материализовать рендж забыли, не эквивалентный код получается
Ещё одна проблема этого кода — несоответствие спецификации. Удаляется по 2 элемента с начала и с конца. Но даже в тестовых данных, после сортировки нужно удалить 3 элемента с начала, т.к. элементы не уникальные (единица повторяется).
НЛО прилетело и опубликовало эту надпись здесь

Да наоборот же проще стало, раньше чтобы посчитать количество слов в файле на гигабайт надо было как-то напрягаться, писать что-то, а теперь будет что-нибудь типа (rf::file(Name) | rs::to_string | rv::split).size().
Хотя в реальности конечно это очередной новый стандарт, который заменит все предыдущие (нет), а вас будет ждать еще больше сюрпризов при работе с будущими легаси-проектами, в которых будут еще и ranges в самых неожиданных местах и кейсах использования, ибо фантазия людская безгранична, и ознакомившись с теми же ренжами некоторые будут делать через них вообще всё, даже если на голом си это было бы в 5 раз короче.

Думаю С можете оставить :)

НЛО прилетело и опубликовало эту надпись здесь
Эмм, не правильно прочитал, написал, потом пересмотрел код и понял, можно ли как-то комментарий удалить?
Клёво. Мне подобной библиотеки в C++ не хватало.
А MooNDeaR хорошие вопросы задаёт. Кто нибудь может прокомментировать, как обстоят дела с исключениями?
А чего именно из boost::adaptors вам не хватало?
Верно ли, что вычисления будут ленивыми, что сначала не вычислится весь reverse в какую-нибудь временную переменную, потом все это не отфильтруется еще куда-нибудь, только чтобы взять 3 элемента с соответствующим расходом времени и памяти?
Верно ли, что после take(3) вычисление reverse прекратится, как и весь алгоритм?
auto v = rs::iota_view(101, 201)
   	| rv::reverse
   	| rv::filter([](auto v) {return v % 7 == 0; })
   	| rv::take(3);
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Что поделать…
C++ имеет возможность переопределения операторов, а у ребят из комитета есть жгучее желание эту возможность использовать в стиле "ыыы, смотрите как можно".


Лично я бы допилил uniform function call syntax и сделал бы без извращенств с операторами. Вместо


for (auto const i : v | rv::drop(2) | rv::take(3) | rv::filter(is_even))

хочу видеть так:


for (auto const i : v.drop(2).take(3).filter(is_even))
C++ имеет возможность переопределения операторов, а у ребят из комитета есть жгучее желание эту возможность использовать

И в чём же проблема? Какая разница как это сделано? Оператором или каким-нибудь кейвордом/конструкциями?
в стиле «ыыы, смотрите как можно».

В стиле «ыыы, мы можем использовать возможности языка и делать нормальные api»? Как это глупо, как это странно.

Лично я бы допилил uniform function call syntax

Это такая же перегрузка не очевидная. В чём разница? Да и что будем делать с коллизиями имён?

без извращенств с операторами

В чём «извращенств» заключается?

хочу видеть так:

У этого подхода все аналогичные проблемы(хотя я наличия проблем не вижу ни там, ни там). Допустим, если я хочу сохранить операцию — можно сделать auto op = filter(is_even); Если же я захочу сделать это с функций — мне нужно описывать функцию с достаточно не очевидным параметром. Какой концепт мы напишем у op? Какой он у filter? Если мы просто напишем auto — мы получим плохую ошибку. Нам нужно будет писать какой-нибудь кастомный bind.

И в чём же проблема? Какая разница как это сделано? Оператором или каким-нибудь кейвордом/конструкциями? В чём «извращенств» заключается?

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


Это такая же перегрузка не очевидная. В чём разница?

Разница в том, что не переопределяется оператор побитового или.


Да и что будем делать с коллизиями имён?

То же самое, что и в случае операторов.
Кстати, UFCS хорош тем, что в библиотечном шаблонном коде можно писать rv::drop(v, 2), и никакой коллизии не будет.


Допустим, если я хочу сохранить операцию — можно сделать auto op = filter(is_even); Если же я захочу сделать это с функций — мне нужно описывать функцию с достаточно не очевидным параметром.

auto op = [](auto v) -> { return v.filter(is_even); }

Вообще, я довольно много писал на C# с использованием LINQ (а там это сделано через расширения), ни разу не приходилось фильтр сохранять.

Не, первый вариант красивее, с.точками.всё.сливается.и.вообще.ничего.не.понятно
Зачем все так усложнять? было эпиграфом статьи. Я бы добавил во все примеры еще один столбец, написанном на С. Желающие могут сами попробовать написать, и задать себе этот вопрос.

Зачем все так усложнять? было эпиграфом статьи. Я бы добавил во все примеры еще один столбец, написанном на Asm. Желающие могут сами попробовать написать, и задать себе этот вопрос.

P.S. Зачем все так усложнять? было эпиграфом статьи. Я бы добавил во все примеры еще один столбец, написанном на машинных кодах x86. Желающие могут сами попробовать написать, и задать себе этот вопрос.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий