Снова здравствуйте. Перевод следующего материала подготовлен специально для студентов курса «Разработчик C++», занятия по которому стартуют уже 27 июня.



Библиотека Ranges была принята в C++20 на совещании стандартного комитета в Сан-Диего в ноябре прошлого года. Библиотека предоставляет компоненты для обработки диапазонов значений, направленных на упрощение нашего кода. К сожалению, библиотека Ranges не очень хорошо документирована, из-за этого ее труднее понять тем, кто хотел бы ее освоить. Этот пост предназначен для ознакомления с примерами кода, написанного с использованием Ranges и без нее.

Реализация библиотеки Ranges Эрика Ниблера доступна здесь. Она работает с Clang 3.6.2 или новее, gcc 5.2 или новее, и VC ++ 15.9 или новее. Примеры кода ниже были написаны и протестированы с последними версиями компиляторов. Стоит отметить, что эти примеры представляют собой типичные реализации и не обязательно являются единственными решениями, которые можно придумать.

Хотя стандартным пространством имен для библиотеки Ranges является std::ranges, в данной текущей реализации библиотеки оно ranges::v3.

Следующие псевдонимы пространства имен используются в примерах ниже:

namespace rs = ranges::v3;
namespace rv = ranges::v3::view;
namespace ra = ranges::v3::action;

Также, для упрощения, мы будем ссылаться на следующие объекты, функции и лямбды:

std::string to_roman(int value)
{
   std::vector<std::pair<int, char const*>> roman
   {
      { 1000, "M" },{ 900, "CM" },
      { 500, "D" },{ 400, "CD" },
      { 100, "C" },{ 90, "XC" },
      { 50, "L" },{ 40, "XL" },
      { 10, "X" },{ 9, "IX" },
      { 5, "V" },{ 4, "IV" },
      { 1, "I" }
   };
 
   std::string result;
   for (auto const & [d, r]: roman)
   {
      while (value >= d)
      {
     	result += r;
     	value -= d;
      }
   }
 
   return result;
}
 
std::vector<int> v{1,1,2,3,5,8,13,21,34};
 
auto print_elem = [](auto const e) {std::cout << e << '\n'; };
 
auto is_even = [](auto const i) {return i % 2 == 0; };

АПДЕЙТ: Я хотел бы поблагодарить Эрика Ниблера и всех остальных, кто комментировал ниже, с предложениями для этих примеров кода. Я обновил несколько на основе их отзывов.

Вывести все элементы диапазона:

До Ranges После Ranges
С++ С++
std::for_each(
   std::cbegin(v), std::cend(v),
   print_elem);
 
// or
  
for(auto const i : v)
{
   print_elem(i);
};
rs::for_each(
   std::cbegin(v), std::cend(v),
   print_elem);
 
// or
 
rs::for_each(std::as_const(v), print_elem);



Выведите все элементы диапазона в обратном порядке:

До Ranges После Ranges
С++ С++
std::for_each(
   std::crbegin(v), std::crend(v),
   print_elem);
 
rs::for_each(
   std::crbegin(v), std::crend(v),
   print_elem);
 
// or
 
for (auto const i : v | rv::reverse)
{
   print_elem(i);
};


Выведите только четные элементы диапазона, но в обратном порядке:

До Ranges После Ranges
С++ С++
std::for_each(
   std::crbegin(v), std::crend(v),
   [print_elem](auto const i) {
      if(i % 2 == 0)
     	print_elem(i);
   });
 
for (auto const i : v
                  | rv::reverse
                  | rv::filter(is_even))
{
   print_elem(i);
};


Пропустите первые два элемента диапазона и выведите только четные из следующих трех:

До Ranges После Ranges
С++ С++
 
auto it = std::cbegin(v);
std::advance(it, 2);
auto ix = 0;
while (it != std::cend(v) && ix++ < 3)
{
   if (is_even(*it))
      print_elem(*it);
   it++;
}
 
for (auto const i : v
                  | rv::drop(2)
                  | rv::take(3)
                  | rv::filter(is_even))
{
   print_elem(i);
};


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

До Ranges После Ranges
С++ С++
for (int n = 101; n <= 200; ++n)
{
   print_elem(n);
}
 
for (auto n : rs::iota_view(101, 201))
{
   print_elem(n);
}


Выведите все римские цифры от 101 до 200. Чтобы преобразовать число в соответствующее римское число, используется функция to_roman(), показанная выше.

До Ranges После Ranges
С++ С++
 
for (int i = 101; i <= 200; ++i)
{
   print_elem(to_roman(i));
}
 
for (auto n : rs::iota_view(101, 201)
            | rv::transform(to_roman))
{
   print_elem(n);
}
 
// or
 
rs::for_each(rv::iota(101, 201),
         	print_element, to_roman);


Выведите римские цифры последних трех чисел, делимых на 7 в диапазоне [101, 200], в обратном порядке.

До Ranges После Ranges
С++ С++
 
for (int n = 200, count=0; n >= 101 && count < 3; --n)
{
   if (n % 7 == 0)
   {
      print_elem(to_roman(n));
      count++;
   }
}
 
for (auto n : rs::iota_view(101, 201)
            | rv::reverse
            | rv::filter([](auto v) {
                return v % 7 == 0; })
            | rv::transform(to_roman)
            | rv::take(3))
{
   print_elem(n);
}


Создайте диапазон строк, содержащий римские цифры последних трех чисел, кратных 7 в диапазоне [101, 200], в обратном порядке.

До Ranges После Ranges
С++ С++
 
std::vector<std::string> v;
for (int n = 200, count = 0;
 	n >= 101 && count < 3; --n)
{
   if (n % 7 == 0)
   {
      v.push_back(to_roman(n));
      count++;
   }
}
auto v = rs::iota_view(101, 201)
   	| rv::reverse
   	| rv::filter([](auto v) {return v % 7 == 0; })
   	| rv::transform(to_roman)
   	| rv::take(3)
   	| rs::to_vector;


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

До Ranges После Ranges
С++ С++
 
std::vector<int> v{ 21, 1, 3, 8, 13, 1, 5, 2 };
 
std::sort(std::begin(v), std::end(v));
v.erase(
   std::unique(std::begin(v), std::end(v)),
   std::end(v));
std::reverse(std::begin(v), std::end(v));
 
std::vector<int> v{ 21, 1, 3, 8, 13, 1, 5, 2 };
 
v = std::move(v) |
    ra::sort |
    ra::unique |
    ra::reverse;


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

До Ranges После Ranges
С++ С++
 
std::vector<int> v{ 21, 1, 3, 8, 13, 1, 5, 2 };
std::vector<int> v2 = v;
std::sort(std::begin(v2), std::end(v2));
      
auto first = std::begin(v2);
std::advance(first, 2);
auto last = first;
std::advance(last, std::size(v2) - 4);
 
v2.erase(last, std::end(v2));
v2.erase(std::begin(v2), first);
 
std::vector<int> v{ 21, 1, 3, 8, 13, 1, 5, 2 };
auto v2 = v |
          rs::copy |
          ra::sort |
          ra::slice(2, rs::end - 2);


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

До Ranges После Ranges
С++ С++
 
std::vector<std::string> words {
   "Lorem", " ", "ipsum", " ",
   "dolor", " ", "sit", " ",
   "amet"};
 
std::string text;
for (auto const & word : words)
   text += word;
 
std::vector<std::string> words {
   "Lorem", " ", "ipsum", " ",
   "dolor", " ", "sit", " ",
   "amet"};
 
std::string text = words |
               	rs::move |
               	ra::join;


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

До Ranges После Ranges
С++ С++

auto text = "Lorem ipsum dolor sit amet";
 
std::istringstream iss(text);
std::vector<std::string> words(
   std::istream_iterator<std::string>{iss},
   std::istream_iterator<std::string>());
auto count = words.size();
 
 
// or
 
size_t count = 0;
std::vector<std::string> words;
std::string token;
std::istringstream tokenStream(text);
while (std::getline(tokenStream, token, ' '))
{
   ++count;
}

auto text = "Lorem ipsum dolor sit amet";
 
auto count = rs::distance(
   rv::c_str(text) | rv::split(' '));


Была ли статья полезной для вас? Пишите в комментарии.

Комментарии (45)


  1. MooNDeaR
    17.06.2019 18:28
    +2

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


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


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


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


    1. 0xd34df00d
      17.06.2019 22:34
      +3

      Причем особенно интересует режим отладки.

      Ну чего вы, нормально же общались.


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


      1. Antervis
        18.06.2019 11:37

        Современные плюсы очень полагаются на оптимизатор.

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

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


        1. mapron
          18.06.2019 15:36

          Ну pragma optimize в MSVC есть, и он худо-бедо-костыльно эту проблему решает. Я для кода, который требовал тяжелых хидеров, для дебага делал так — для дебаг конфигурации форсился флаг /O2, а после подключения всех заголовков, для своего уже кода вставлялась pragma. Да, многословно и костыльно, но по итогу делало то что нужно.


        1. 0xd34df00d
          18.06.2019 17:56

          современные плюсы (если говорить именно о коде стандартной библиотеки) сильно полагаются на одну оптимизацию — инлайнинг.

          Это, конечно, ключевая оптимизация, но далеко не единственная. Инлайнинг позволяет коду из заинлайненной функции быть ближе к контексту и открывает возможности векторизации, всяких там peephole и тому подобных.


          Которую для полноценной отладки необходимо отключать.

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


          Конечно, инлайнинг не даст вам полноценный стек (а последующие оптимизации не дадут значения переменных, будет одно сплошное optimized out), но конструктивно отлаживаться всё равно можно. По моему опыту, опять же, по крайней мере.


          На мой взгляд, компиляторам просто нужен флажок (по умолчанию) для инлайнинга функций/методов из стандартной библиотеки, даже в дебаг режиме.

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


          1. Antervis
            19.06.2019 18:24

            Конечно, инлайнинг не даст вам полноценный стек (а последующие оптимизации не дадут значения переменных, будет одно сплошное optimized out), но конструктивно отлаживаться всё равно можно. По моему опыту, опять же, по крайней мере.

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

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

            Пользовательские лямбды — нет, очевидно.


      1. twinklede
        18.06.2019 11:43

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

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

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

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

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

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

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

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


        1. domix32
          18.06.2019 12:38

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

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


          1. twinklede
            18.06.2019 13:13

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

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


            1. domix32
              18.06.2019 22:44

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


        1. 0xd34df00d
          18.06.2019 17:58

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

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


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


          В ситуации же с более мощной и универсальной отладкой трейсом — таких проблем нет.

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


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


          1. twinklede
            18.06.2019 18:15

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

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

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

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

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

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


            1. 0xd34df00d
              18.06.2019 18:42

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

              Даже как memcpy в заmmapленный файл.


              1. twinklede
                18.06.2019 19:09

                Даже как memcpy в заmmapленный файл.

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

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


    1. twinklede
      18.06.2019 13:46

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

      Замерять нужно уже реализации из 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 контексте?

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


  1. Antervis
    17.06.2019 18:29
    +1

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

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

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

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

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

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

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

    если не обрабатывать ошибки, то проще вернуть std::count(..., ' ') + 1. А вообще используйте isspace, хорошая штука


  1. PerlPower
    17.06.2019 23:03
    +4

    Все, сдаюсь! Больше не буду писать в резюме знание C/C++.


    1. dipsy
      18.06.2019 05:14

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


    1. M_AJ
      18.06.2019 08:35

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


  1. Filippok
    18.06.2019 04:03

    Пора на пенсию...



  1. Dogrtt
    18.06.2019 10:39

    Эмм, не правильно прочитал, написал, потом пересмотрел код и понял, можно ли как-то комментарий удалить?


  1. 1KoT1
    18.06.2019 11:19

    Клёво. Мне подобной библиотеки в C++ не хватало.
    А MooNDeaR хорошие вопросы задаёт. Кто нибудь может прокомментировать, как обстоят дела с исключениями?


  1. iHateCpp
    18.06.2019 15:19

    Уффффф…


  1. opaopa
    18.06.2019 15:43

    Верно ли, что вычисления будут ленивыми, что сначала не вычислится весь 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);
    


    1. 0xd34df00d
      18.06.2019 17:52

      Да, views как раз представляют такие ленивые диапазоны.


  1. realimba
    18.06.2019 17:51
    +1

    Похоже ребята из комитета в школе увлекались перлом/бреинфаком. Или просто ненавидят всех инженеров на планете. В любом случае не завидую тому кто такие поделки будет дебажить.


    1. DistortNeo
      18.06.2019 19:26

      Что поделать…
      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))


      1. twinklede
        18.06.2019 19:53

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

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

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

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

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

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

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

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

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


        1. DistortNeo
          18.06.2019 21:12

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

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


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

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


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

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


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

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

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


          1. twinklede
            18.06.2019 21:58

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

            Это какой-то догматизм особенно в контексте |, который везде пайп. А навешивание на что угодно функционала «может» затруднить чтение кода. Чем упомянутый linq отличается? Тем, что это набор кейвордов?

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

            Нет такого оператора и это не разница. Есть оператор|. Он никак не привязан к какому-либо или в C++. К тому же множество людей воспринимают его за пайп. И семантически он им и является.

            К тому же, как я уже писал(и что было успешно проигнорировано), UFCS является так же подвидом перегрузки. Т.е. он расширяет семантику точки, добавляя ей новое поведение. И это поведение, по моему мнению, куда более не очевидное, чем операторы.

            То же самое, что и в случае операторов.

            Что за глупости? v | emplace спокойно будет работать.

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

            Зачем он тогда нужен, в данном случае? В случае v.drop может быть конфликт с v::drop и drop(v);

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

            К чему это было написано? Я заранее дал ответ на вопрос, а так же заранее дал ответ по поводу auto. К тому же конструкция синтаксически неверна.

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

            А к чему это было написано? Причём тут фильтр, если это просто пример? Сохранить набор операций через op = a | b | c куда проще. Это очевидно. Зачем его сохранять так же очевидно.

            Даже если не учитывать большую громоздкость функции, то проблема с auto никуда не делась. auto пропустит любой тип, а не ренж(либо то, что нужно). С этой проблемой и борются концепты на базе которых и сделан ranges. range — это базовый концепт.

            Вроде как завезут сокращённый синтаксис и можно будет написать (range v), но это не всегда будет работать. Вполне вероятно, что какой-то набор операций может ограничить тип. К тому же операцию можно применить не только к ренжу, но с смержить с другой операцией.

            Фундаментально a | b | c куда более мощная конструкция, она может породить какой угодно тип. Если же мы засунем её в функцию — мы в общем случае можем написать лишь auto(и то там не просто auto. Там множество нюансов с const/& и т.п.).

            Но самая важная проблема в том, что мы получить ошибку в функции op(в её теле), а не где-либо ещё. Если мы напишем x | op — мы получим «нету оператора между x и op». В случае же с x.op() — мы не получим ошибку. В op x спокойно передастся и уже внутри op будет ошибка, что нету функции «неведомая херня от x». Эта ошибка мало то, что непонятна дак ещё и будет находится хрен пойми где и нужно будет смотреть трейс.


            1. DistortNeo
              18.06.2019 22:12

              Чем упомянутый linq отличается? Тем, что это набор кейвордов?

              Нет. Это те же самые функции (.Select(...), .Where(...), .Take(...)).
              Плюс есть синтаксический сахар в виде LINQ-конструкций с ключевыми словами, но мне он не очень нравится.


              Нет такого оператора и это не разница. Есть оператор|

              В документации это "bitwise arithmetic operators". А ещё "The canonical implementations usually follow the pattern for binary arithmetic operators described above".


              Зачем он тогда нужен, в данном случае? В случае v.drop может быть конфликт с v::drop и drop(v);

              В пользовательском коде это не является проблемой.


              Сохранить набор операций через op = a | b | c куда проще.

              А вы уверены, что такой функционал есть? Насколько я понял, оператор | определён между range и фильтром, но не между двумя фильтрами.


              Если мы напишем x | op — мы получим «нету оператора между x и op». В случае же с x.op() — мы не получим ошибку

              Почему? Будет точно такая же ошибка: у x нет метода op.


              1. twinklede
                18.06.2019 22:43

                Плюс есть синтаксический сахар в виде LINQ-конструкций с ключевыми словами, но мне он не очень нравится.

                Я об этом и говорил. Все пользуются сахаром и никаких проблем нет. В баше пользуются пайпами и никаких проблем нет.

                В документации это «bitwise arithmetic operators». А ещё «The canonical implementations usually follow the pattern for binary arithmetic operators described above».

                Нет, это не описание operator| и это очевидно(потому что семантику у него куда шире). Подобное название, даже если оно где-то используется — это лишь имя собственное. А описание на которое вы ссылаетесь — это не описание оператора|, а описания конкретной перегрузки/стандартного оператора, если проще.

                Аналогично описании функции max не описывает понятие «функция», а описывает семантику конкретной функции. Таких функции, как и перегрузок оператора в стандарте множество. Операторы/функции описаны отдельно.

                В пользовательском коде это не является проблемой.

                Является. Проблему я вам показал. У меня есть операция drop и есть метод drop — я получу коллизию. Таким образом я в своих контейнерах/ренжах не должен буду использовать имя метода drop и множество других, определённых в ranges.

                А вы уверены, что такой функционал есть?

                Этот функционал элементарен и предсказуем. И да, он есть.

                Почему? Будет точно такая же ошибка: у x нет метода op.

                Не будет. Вы запутались. Очередной подтверждение моим словам об UFCS и том, что это тоже что и operator|, если не хуже. Здесь никакого вызова метода op нет, есть вызов функции op от x. x.op -> op(x). И далее т.к. параметр auto, то вызов op произойдёт, но далее(при инстанцировании тела) получится ошибка.

                godbolt.org/z/q0pB7o — как-то так.


                1. DistortNeo
                  18.06.2019 23:14

                  Я об этом и говорил. Все пользуются сахаром и никаких проблем нет. В баше пользуются пайпами и никаких проблем нет.

                  Причём тут баш? В баше вам не нужно с числами играть.
                  А вот в подавляющем числе ЯП этот оператор имеет вполне себе однозначную семантику.


                  У меня есть операция drop и есть метод drop — я получу коллизию

                  Ничего страшного. Напишете не v.drop(), а drop(v).


                  Не будет. Вы запутались. Очередной подтверждение моим словам об UFCS и том, что это тоже что и operator|, если не хуже. Здесь никакого вызова метода op нет, есть вызов функции op от x. x.op -> op(x). И далее т.к. параметр auto, то вызов op произойдёт, но далее(при инстанцировании тела) получится ошибка.

                  Оператор — точно такой же метод. Вот так будет с оператором:
                  https://godbolt.org/z/DaBLrW


                  1. twinklede
                    18.06.2019 23:33

                    Причём тут баш? В баше вам не нужно с числами играть.
                    А вот в подавляющем числе ЯП этот оператор имеет вполне себе однозначную семантику.

                    Ваши рассуждения не имеют смысла. В большинстве языков нету linq. На этом можно закончить — linq ненужен. Отсылки к числам так же не работают. Существует множество чего угодно, что работает с числами/не числами по-разному. Любая перегрузка чего-либо работает так. К тому же, в большинстве языков UFCS. И на этом так же можно закончить.

                    Ничего страшного. Напишете не v.drop(), а drop(v).

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

                    Оператор — точно такой же метод.

                    Объясните мне, зачем вы со мною спорите? Вы уже насобирали с десяток факапов, зачем вы продолжаете спорить с очевидным исходом?

                    Вот так будет с оператором:
                    godbolt.org/z/DaBLrW

                    Вы ничего не поняли. В ситуации с операторами не будет подобной перегрузки, т.к. не будет функции. Конструкции auto op = a | b | c; не порождает функцию, а порождает инстанс кого-либо класса и ошибка будет там, где нужно.

                    godbolt.org/z/VSykII — я уже показывал ошибку. Где же здесь ошибка в operator|? Нету.


                    1. DistortNeo
                      18.06.2019 23:52

                      В большинстве языков нету linq. На этом можно закончить — linq ненужен.

                      Подобный функционал уже есть в C#, Java, JavaScript, и он работает единообразно. Нужная штука. Но C++ решил пойти своей дорогой.


                      К тому же, в большинстве языков нет UFCS. И на этом так же можно закончить.

                      В большинстве языков много чего нет из того, что есть в С++. Получается, С++ не нужен? Какой-то философский спор получается.


                      Объясните мне, зачем вы со мною спорите? Вы уже насобирали с десяток факапов, зачем вы продолжаете спорить с очевидным исходом?

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


                      Вы ничего не поняли. В ситуации с операторами не будет подобной перегрузки, т.к. не будет функции.

                      Что? Оператор — это точно такая же функция. Наличие/отсутствие перегрузки определяется аргументами и трейтами.


                      Конструкции auto op = a | b | c; не порождает функцию, а порождает инстанс кого-либо класса и ошибка будет там, где нужно.

                      Ошибка в любом случае будет там, где нужно. Просто пришлось бы задизайнить так, что b, c стали бы функциями, а не объектами-состояниями.


                      1. twinklede
                        19.06.2019 00:16

                        Подобный функционал уже есть в C#, Java, JavaScript, и он работает единообразно. Нужная штука. Но C++ решил пойти своей дорогой.

                        Где это в js есть linq? Вы уж определитесь там, С++ пошел своей дорогой, либо нам нужно ссылаться на «большинство языков». Если ссылаться — linq и любые уникальные фишки языков ненужны, но я думаю, что несостоятельность подобных идей очевидна?

                        В большинстве языков много чего нет из того, что есть в С++. Получается, С++ не нужен? Какой-то философский спор получается.

                        Ну это ваша логика. Очевидно, что я с этим не согласен. Какая логика — такие и выводы.

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

                        Ваш вариант — дырявый. Вы не обосновали «не совсем удачное» и прочее. Именно с этим я и спорил и именно в этой темы вы собрали десятки факапов. Зачем начинать и продолжать в подобной ситуации?

                        Что? Оператор — это точно такая же функция. Наличие/отсутствие перегрузки определяется аргументами и трейтами.

                        Ещё раз, это полная чушь. Вы явно не понимание того, о чём пытаетесь рассуждать. Это либо глупость, либо троллинг. Вы не знаете даже языка(в необходимой для рассуждения на подобные темы мере), о котором пытаетесь рассуждать.

                        Ошибка в любом случае будет там, где нужно. Просто пришлось бы задизайнить так, что b, c стали бы функциями, а не объектами-состояниями.

                        Вам уже объяснили, что не так. Ваши рассуждения неверны.

                        Абсолютно неважно чем будет a, b или c. У меня такое ощущение, что вы просто говорите рандомные фразы без какого-либо понимания/учёта контекста. Ну будут они функциями — дальше что? Что это изменить то должно? Да ничего это не изменит.

                        Ещё раз вам повторяю. Фундаментальное отличие тут в том, что в одной случае функция, а в другом нет. Всё. Вы что угодно делайте — вы этого не измените. operator| и ranges вообще никакого отношения к теме не имеет.

                        Давайте ещё раз. В ситуации текущей op будет объектом. В рамках описанной ваши модели — функцией. Всё. Зачем вы пишите какую-то глупость про операторы, про какие-то функции и прочее? У вас нету операторов, нету. Вы запутались.

                        Забудьте об operator| из ranges, забудьте об теле функции в вашем op. Всё это неважно. Всё это не имеет значения.

                        Давайте проще. Напишите пример, в котором у вас будет ошибка «там где нужно». Это 10 строчек кода, это несложно.


                        1. DistortNeo
                          19.06.2019 00:42

                          Где это в js есть linq?

                          Функции map, filter, reduce + остальное можно реализовать сторонними библиотеками, как это обычно делается в JS.
                          Под LINQ я понимаю не конкретную реализацию, а обработку коллекций chained-методами, желательно с ленивыми вычислениями.


                          Если ссылаться — linq и любые уникальные фишки языков ненужны

                          Почему?


                          Давайте проще. Напишите пример, в котором у вас будет ошибка «там где нужно». Это 10 строчек кода, это несложно.

                          Да пожалуйста:
                          https://godbolt.org/z/t_3Xcw


                          P.S. На флеймовую часть комментария не вижу смысла. Вы либо не хотите понимаете, что я хочу сказать, либо не приемлете иные точки зрения.


                          Я же хочу просто писать меньше букв:


                          auto short_syntax = arr.sorted().skip(1).take(5);
                          
                          auto long_syntax = arr | ranges::action::sort | ranges::view::skip(1) ranges::view::take(5);


                          1. twinklede
                            19.06.2019 08:26

                            Функции map, filter, reduce + остальное можно реализовать сторонними библиотеками, как это обычно делается в JS.

                            Никакого отношения к linq не имеет, к тому же основной фичой там является тот самый сахар.

                            Под LINQ я понимаю не конкретную реализацию, а обработку коллекций chained-методами, желательно с ленивыми вычислениями.

                            Там нету ленивых вычислений, там нету попросту нечего.

                            Почему?

                            Потому что аналога linq нету в множестве языков. А значит оно ненужно. По вашей же логики. Ведь перегрузки нету и именно поэтому плохая и ненужная.

                            Да пожалуйста:

                            Неверно. Мне нужно с этим: auto op = [](auto v) -> { return v.filter(is_even); }

                            Ах да, я не буду позволять вам врать, менять тему и заниматься манипуляциями, если подобное повториться.

                            P.S. На флеймовую часть комментария не вижу смысла. Вы либо не хотите понимаете, что я хочу сказать, либо не приемлете иные точки зрения.

                            Вы пишите рандомные фразы без какого-либо понимания. Это уже было множество раз мною показано. Я лишь не акцентировал на этом внимании и если вы продолжите играть «в не понимаю» я на них внимание акцентирую.

                            Я же хочу просто писать меньше букв:

                            Ну раз откровенное враньё продолжается — буду действовать иначе. Ваша задача заставить это: godbolt.org/z/l9LpNw работать. Именно в таком виде. Без решения все ваши рассуждения не работают.


                            1. DistortNeo
                              19.06.2019 11:12

                              Потому что аналога linq нету в множестве языков. А значит оно ненужно.

                              И именно поэтому люди дописывают его в виде библиотек. Правда, зачем?


                              Мне нужно с этим: auto op = [](auto v) -> { return v.filter(is_even); }

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


                              Ваша задача заставить это: godbolt.org/z/l9LpNw работать

                              Что изменилось, кроме того, что функция take оказалась засунута в namespace?


                              Ваши рассуждения не имеют смысла. Вы уже насобирали с десяток факапов, зачем вы продолжаете спорить с очевидным исходом? Вы ничего не поняли. Ещё раз, это полная чушь. Вы явно не понимание того, о чём пытаетесь рассуждать. Это либо глупость, либо троллинг. Вы пишите рандомные фразы без какого-либо понимания. Ну раз откровенное враньё продолжается — буду действовать иначе.

                              Вы считаете это адекватным диалогом?


                              Хорошо, убедили. Оператор | — это единственно верное и правильное решение, и других вариантов быть не должно даже в мыслях.


                              1. twinklede
                                19.06.2019 11:52

                                И именно поэтому люди дописывают его в виде библиотек. Правда, зачем?

                                То, что оно ненужно утверждаете вы, а не я. Я то как раз выступаю за обратное.

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

                                Мне не нравиться враньё и клоунада. Я вам изначально сказал, что auto не работает и нужен концепт. Вы с этим спорили. Потом бам и риторика поменялось и вы начали мне показывать концепт.

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

                                Что изменилось, кроме того, что функция take оказалась засунута в namespace?

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

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

                                Но вы не понимаете как это работает, вы даже макро-нюансов каждого из решения не понимаете.

                                Банальный пример я вам уже привёл. op — это свободная функция и должна находится в неймпейсе(раз операции у вас находятся в неймспейсе). Это значит, что вызвать op(x) нельзя, вообще.

                                Вызвать его можно только при помощи adl. Но для этого, условно, op должна находится в неймспейсе связанном с x. Есть у нас вектор в std::, а операция в ranges:: — оно никак не вызовется и ваш код попросту не будет работать.

                                Если же вы попытаетесь это исправить(в общем случае у вас это не получится), а где получится — там adl будет работать и для | а значит для операций ненужно указывать неймпейсы. Но вы их указали. Это ещё одна ваша ошибка.

                                Вы считаете это адекватным диалогом?

                                Адекватным диалог это перестало быть когда вы начали откровенно игнорировать мои тезисы и писать мне в ответ глупости.

                                Я говорю «можно использовать А, но на самом деле нет потому-то». Адекватный человек напишет в ответ «можно использовать А»? Нет. Это настолько нелепый треш, что адекватность подобных ответов и их автора под вопросов. Но вы написали.

                                Хорошо, убедили. Оператор | — это единственно верное и правильное решение, и других вариантов быть не должно даже в мыслях.

                                Это глупость. Я здесь не обсуждаю другие варианты — я обсуждаю то, что вы выдаёте под видом решения.

                                Зачем вы опять занимаетесь подобным? Зачем подменяете тему? Я нигде не говорил подобного: «Оператор | — это единственно верное и правильное решение».

                                Ситуация в следующем. Приходит человек и говорит «я сяду на ракету и получу», ему отвечают «нет, не полетишь». И после того как выяснилось множество пробелов в его понимании он начинает «хорошо, убедили. Ракеты не летают и люди в них тоже». Да, действительно.

                                Только речь шла о другом. Эта типичная манипуляция.


                                1. DistortNeo
                                  19.06.2019 12:41

                                  То, что оно ненужно утверждаете вы, а не я. Я то как раз выступаю за обратное.

                                  А это кто написал?


                                  Ваши рассуждения не имеют смысла. В большинстве языков нету linq. На этом можно закончить — linq ненужен.


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

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


                                  Ещё наткнулся вот на такой пост:
                                  https://dragly.org/2017/03/31/uniform-call-syntax/


                                  Видимо, использование | — это теперь позиционируется как альтернатива UFCS. Придумали костыль, а оказалось, костыль-то рабочий. Ну и что, что теперь аргументы нужно дополнительно обернуть? Работает же!


                                  P.S. А лично мне действительно интересно, можно ли обойтись без оператора | и писать:


                                  auto stored_action = ns::take(1).split(' ');
                                  auto stored_view = stored_action(view);
                                  auto stored_view_2 = view.take(1).split(' ');

                                  Существующие велосипедные решения предполагают, что коллекция обёрнута в view, а .take, .split являются её методами. Минус — необходимо писать ns::view(x).take(1) вместо x.take(1).


                                  1. twinklede
                                    19.06.2019 13:02

                                    Ваши рассуждения не имеют смысла. В большинстве языков нету linq. На этом можно закончить — linq ненужен.

                                    Это не моя тезис, а ваш. Вы показали простую схему «есть фича -> „нету/не использует/непривычно в других языках -> фича ненужна“. Я использовал эту формулу и получил тот тезис.

                                    Для понимания текста ненужно дёргать отдельные фразы, фразы существуют в контексте.

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

                                    Тогда тоже самое можно сделать и для |. В любом случае для | без проблем можно сделать adl.

                                    Но в любом случае решение с | мощнее, либо как минимум не хуже. Но это не значит, что UFCS в каком-то виде ненужен.

                                    P.S. А лично мне действительно интересно, можно ли обойтись без оператора | и писать:

                                    Можно в случае с stored_action/stored_view. Если там где-то будет использовать внешняя база — нет. Если view это не внутренняя структура библиотеки — нельзя.

                                    Существующие велосипедные решения предполагают, что коллекция обёрнута в view, а .take, .split являются её методами. Минус — необходимо писать ns::view(x).take(1) вместо x.take(1).

                                    Ну да, нужна база. Либо так, либо сделать какой-нибудь метод в который передавать x в конце цепочки. Либо view как здесь.

                                    Но в любом случае это менее универсально и требует множества лапши в каждой операции. Тут нужны какие-нибудь мета-классы что-бы автоматически генерировались все эти методы.


                                    1. DistortNeo
                                      19.06.2019 16:26

                                      Это не моя тезис, а ваш. Вы показали простую схему «есть фича -> „нету/не использует/непривычно в других языках -> фича ненужна“. Я использовал эту формулу и получил тот тезис.

                                      Логический вывод сделан неверно. Моя позиция: "есть фича, но она сделана не так, как в других языках, где она тоже есть -> возможно, неудачная реализация".


                                      Можно в случае с stored_action/stored_view. Если там где-то будет использовать внешняя база — нет. Если view это не внутренняя структура библиотеки — нельзя.

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


                                      либо сделать какой-нибудь метод в который передавать x в конце цепочки.

                                      Сложновато, да и с zip могут быть проблемы.


      1. dipsy
        19.06.2019 14:01

        Не, первый вариант красивее, с.точками.всё.сливается.и.вообще.ничего.не.понятно