Pull to refresh

Comments 63

for (int i : view::iota(0, days))
Интересно, насколько сложно было бы сделать iota без первого параметра:
for (int i : view::iota(days))
Ну и вопрос чуть более глубокий, раз уж взялись ломать for в С++11 и внедрили Range-based for loop, может быть можно было таки ещё немного его упростить, сделать как в питоне:
for i in view::iota(days)
iota с одним параметром тоже есть. Но она для бесконечных циклов (указывается начальное число). Пример использования:
for (int i : std::views::iota(1) | std::views::take(9))
    std::cout << i << ' '; // 1 2 3 4 5 6 7 8 9 
std::cout << '\n';


А что касается второго — как-то неплюсово выглядит) И ещё не ясно, где заканчивается диапазон. Ещё одна проблема — in не ключевое слово. В C++ синтаксис адский, такие простые решения не прокатят :(
Ну и вопрос чуть более глубокий, раз уж взялись ломать for в С++11 и внедрили Range-based for loop, может быть можно было таки ещё немного его упростить, сделать как в питоне
а зачем?

Чтобы было больше похоже на питончик же! Симулятор родной атмосферы.

UFO just landed and posted this here
Ну, когда я пишу на js, я тоже расставляю точки с запятой руками, хотя прекрасно знаю что есть ASI. Тоже симулирую :)

Может тогда уж сразу for i in 0..days без всякой iota?

for i in days и договорились

а сделают скорее всего вот так
std::loop_helper::for i std::loop_helper::in days -> () { ... }

Так непонятно, с чего счет начинается :)

std::loop_helper::for i std::loop_helper::in days

Воо, вот это я понимаю, богоугодная фигня!

Стоит добавить, что есть ещё и range-v3. Можно легко попробовать со всеми основными компиляторами и подключить к проекту с помощью какого-нибудь vcpkg. Ну и boost::range, правда не уверен, насколько это живо.

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


  1. Оператор |. Для его реализации требуется определённое шаблонное шаманство, пусть и не сильно сложное. Что менее приятно, для реализации своих рейнджей требуется определённая писанина. Увы, комитет не смог или не захотел внести оператор |> или какой-то аналог.
  2. Насколько я знаю, с поддержкой корутин всё непросто. Написать просто функцию с yield или аналогом, чтобы она сразу стала range-compliant, не выйдет.
  3. Не совсем понятна ситуация с адаптированием своих контейнеров к поддержке рейнджей. До С++17 включительно iterator adaptors в std не пахло.
Увы, комитет не смог или не захотел внести оператор |> или какой-то аналог.
ведь нет никакой разницы каким именно оператором склеиваются ренджи, резолв перегрузок с вычислением шаблонных параметров будет одинаково шаманистым.
Насколько я знаю, с поддержкой корутин всё непросто. Написать просто функцию с yield или аналогом, чтобы она сразу стала range-compliant, не выйдет.
а в чем конкретно сложность? Написать ленивый генератор на корутинах не сложно, ровно как и реализовать для него begin()/end()
Не совсем понятна ситуация с адаптированием своих контейнеров к поддержке рейнджей
опять же, в чем сложность? Ренджи должны принимать любые контейнеры (т.к. оперируют begin()/end(), которые должны быть определены для любого контейнера). Единственная потенциальная сложность в том, что некоторые методы контейнеров принимают одинаковые типы begin/end итераторов, что для ренджей в общем случае не так. Однако и эта проблема решается с помощью common_view, хоть это и не шибко удобно
ведь нет никакой разницы каким именно оператором склеиваются ренджи, резолв перегрузок с вычислением шаблонных параметров будет одинаково шаманистым.

Я о том, что наличие оператора, позволяющего записывать вызов функции


result range_op(input_range input, arg arg1)

как


input |> range_op(arg1)

сильно упростило бы дело; однако, сейчас поддержка пайпа требует писать новые операторы в виде


struct range_op: public pipe_operator_right_hand
{
    range_op(arg arg1) { ... }
    result operator()(input_range input) { ... }

    arg m_arg1;
};

а в чем конкретно сложность? Написать ленивый генератор на корутинах не сложно, ровно как и реализовать для него begin()/end()

Может я что-то путаю, но корутины вроде как затачивались для работы исключительно с экзекуторами и Ко? Или их вполне можно использовать в стиле питоновских или сишарповских генераторов?


опять же, в чем сложность? Ренджи должны принимать любые контейнеры (т.к. оперируют begin()/end(), которые должны быть определены для любого контейнера). Единственная потенциальная сложность в том, что некоторые методы контейнеров принимают одинаковые типы begin/end итераторов, что для ренджей в общем случае не так. Однако и эта проблема решается с помощью common_view, хоть это и не шибко удобно

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

сильно упростило бы дело; однако, сейчас поддержка пайпа требует писать новые операторы в виде...
ваш пример уже далек от минимально достаточного, можно просто реализовать
template <std::ranges::range Range>
auto operator | (Range&& rng, my_range_op /*& / &&*/ op) { ... }
Может я что-то путаю, но корутины вроде как затачивались для работы исключительно с экзекуторами и Ко? Или их вполне можно использовать в стиле питоновских или сишарповских генераторов?
вполне можно, вот даже бумага с примерами реализации и использования генератора. Единственное что относительно условной iota генератор на корутине может потребовать одну динамическую аллокацию...
С другой — таскать из проекта в проект очередной iterator_adaptor уже немного утомляет.
а зачем вам адаптер то? Делаете
#include <iterator>
static_assert(std::random_access_iterator<my_iterator>);
и реализовываете методы пока не скомпилится…
Шаблонное шаманство с концептами упростилось, стало выразительным и простым) Это и к п. 3 относится, достаточно, чтобы свой контейнер удовлетворял концепту std::ranges::range
  • Оператор новый вводить и менять 70% языка (а лучше бы выкинуть вообще), к сожалению никто не будет. Комитет сильно трясётся над обратной совместимостью, жертвуя при этом возможностями для эволюции языка. В общем есть свои плюсы и минусы, но печали тут больше. После того, как ввели префикс co_ для ключевых слов поддержки сопрограмм, надежда на светлое будущее почти угасла… Даже тут есть своё лобби.
    Касательно простоты добавления |, шаманство действительно требуется, но ranges-v3 предоставляет достаточно высокоуровневые абстракции. Самому там надо написать около 10 строк значимого кода.


  • С корутинами тоже всё не так страшно, если не писать на голой стандартной библиотеки С++ (надеюсь, так никто не делает в жизни). С cppcoro всё довольно просто получается.


  • Касательно адапторов своих контейнеров не уверен. Вполне себе допускаю, что если они поддерживают более-менее классические итераторы с категориями, то оно может и сразу заработать. По крайней мере, внутри ranges-v3 я не помню чтобы видел что-то завязанное на прям контейнеры. Итераторы и их свойства. Но я могу ошибаться.



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


Я какие-то время назад экспериментировал со связкой ренжи + корутины, возможно вам будет интересно: https://habr.com/ru/post/481402/

Oh my God, опять этот блевотно-неймспейсовый ретроградный недосинтаксис :(

Ну неужелили нельзя было сделать как то так:

for (int i : 0...days)...

Или даже

for (int i: days)...

Или делать проще религия не позволяет? :(

Или делать проще религия не позволяет? :(
ох если бы только эти старые деды из комитета со стажем разработки 15+ лет знали насколько синтаксис частного случая важнее функциональности и расширяемости языка…

Почему-то кажется, что суммарный мировой объем "частных случаев" вроде for (int i=0; i < x; ++i) будет на порядок-другой больше, чем вумной темплейтной функциональщины... и тогда вопрос - язык для людей или люди для языка...?

вам кажется. Подавляющее большинство плюсовых циклов в современном коде являются range-based for по коллекции или while.

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

Ну, за 15+ лет работы с С++ я с большой уверенностью могу утверждать, что Вы либо тонко троллите, либо просто не видели «настоящего» продакшен кода и говорите исключительно про опенсорс, где каждый сам себе буратина.
В очень-очень многих фирмах, если кто-либо «внезапно» впилит в проект нечитаемую темплейтную тарабарщину, то довольно быстро получит по шапке от тимлида и быстро побежит переписывать её на «простые циклы». Почему — да всё просто — время разработчиков и так стоит денег, а время на разбирание и фиксы «внезапных запилов» другими людьми — будет стоить ещё дороже.
Ну, за 15+ лет работы с С++ я с большой уверенностью могу утверждать, что Вы либо тонко троллите, либо просто не видели «настоящего» продакшен кода и говорите исключительно про опенсорс, где каждый сам себе буратина.
15 лет работы с с++ и до сих пор используете цикл со счетчиком вместо range based for? А продакшн кода я повидал достаточно, и enable_if'ы и прочее там весьма встречается.
UFO just landed and posted this here

Так о том же и речь, что for (auto i: y) намного лучше выглядит, чем вот эти вот view, iota, etc..

:)

Так смотри, for (auto i: iota(0, 10))
Как мне кажется это не так уж и сложно писать :)
Да и к тому же, есть вероятность, что в будущем введут сахар, как в rust например. То есть for (auto i: 0..10). И это будет заменятся на код выше, но это лишь теория и с++ всегда будет в чем-то хуже других языков.

Нет не позволяет :), стоит посмотреть как проходят комитеты по стандартизации и сколько там задаётся вопросов на тему «а что если у нас тут сферический конь в вакуме окажется» где вы со своей «классной» и «простой» реализации тут же сядете в лужу потому как продходит одна только 2% вопросов и ещё требует изменения в коре. Напомню что C++ это не python в котором фактически 1 человек может решить как все будет работать, стандарт С++ обсуждается сотнями специалистов из десятков разных стран и чтоб добится одобрения большинства нужно очень сильно напрячься

В комитете нет "левых" людей все представители это самые активные разработчики c++ со всех стран мира. Вы говоритео о том о чем судя по всему даже представления не имеете, но "осуждаете". C++ это открытый стандарт любой может предложить свой пропозал от России есть рабочая группа 21 которая за вас может его предоставить на комитете.

Судя по тому, что они вкатывают в стандарт, их "активная разработка" только этим и ограничивается :)

К сожалению, единственный аргумент "сегодня" продолжать использовать с++ - это производительность скомпилированного кода. С каждым новым стандартом болото всё более дремучее :(

Странная логика: ведь никто не запрещает писать в менее дремучем «болоте»: C++11 или даже C++03, если вам так больше нравится. Новый Стандарт добавляет новые возможности и за редчайшим исключением не трогает старые. Как выход нового Стандарта может усложнить вам жизнь?
Ну, как вариант: представьте, что в компанию пришёл новый программист, быстро наваял какой-либо тарабарщины на новейшем стандарте и через полгода вдруг взял и ушёл бороздить просторы (или попал под трамвай). А кто-то должен потом тратить время на разбор и починку гениального, но write-only кода. Иногда очень много времени.
Для таких случаев существуют внутрикорпоративные гайды и код-ревью. Скажем, требование, писать код на C++11. Кроме того, версия Стандарта задаётся в конфигурации компилятора, так что соответствие может проверяться даже автоматически.

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

Но если честно, если кодеры в вашей компании не могут освоить последний Стандарт, то действительно стоит задуматься, нужен ли вам C++, или нужны ли вам эти кодеры… Всё-таки, код должны писать и поддерживать профессионалы
Во-первых, да, гайды действительно есть, и их придумали не случайно, а для поддержания единообразной кодовой базы и, в том числе, именно чтобы противодействовать влиянию таких вот «рыцарей темплейта». Особенно это прижилось в медицинской отрасли — на некоторых конторах даже виртуальные методы не всегда разрешены.

Во-вторых, гораздо проще ввести в курс дела новых коллег, если код проекта выглядит не вырвиглазно, лаконичен и интуитивно понятен.

В-третьих, в B2B довольно распространена практика разработки «на заказ» аутсорсерами или консалтинговыми фирмами. И одно из требований к ним, как правило — это тщательное документирование кода.

И наконец, а с чего Вы взяли, что профессионалы (имею в виду настоящих опытных разработчиков, а не «синьйоров в 25 лет после курсов питона») обязательно должны тупо бросать всё предыдущее и начинать радостно поддерживать «последний стандарт», просто потому что он вышел?
Во-вторых, гораздо проще ввести в курс дела новых коллег, если код проекта выглядит не вырвиглазно, лаконичен и интуитивно понятен.
какой код более лаконичен и интуитивно понятен:
1. с использованием shared_ptr
2. с использованием наследования от самописного класса с рефкаунтингом, для которого нужно везде использовать правильный метод вместо delete
Пример 2: какой код проще читать?
map<string, string> m;
// 1
for (auto it = m.begin(); it != m.end(); ++it) {
    // код c it->first/it->second
}
// 2
for (auto& [key, value] : m) {
    // код с key/value
}
таких примеров, где новый стандарт значительно улучшает читаемость кода, я могу накидать довольно-таки много. Могу так же накидать примеры где за счет шаблонов можно повысить переиспользуемость и надежность кода.

Но нет, «проще» ведь сидеть писать на с++03 и критиковать как его синтаксис, так и нововведения, призванные его улучшить…
Хм, ну Вы тут как раз привели пример очень хорошего и нужного расширения :) Собственно, это практически то же, что я писал насчет ренжей, почему бы их не реализовать согласно аналогичным вещам с for:

for (int i = a...b)

что было бы эквивалентно интервалу [a,b)

или более явно при необходимости:

for (auto x = [4...10])

Получается, «комитет» со своими кривыми view::iota противоречит своим же предыдущим решениям…
не вижу смысла вводить operator… и парсинг скобочных юзкейсов ради диапазона целых чисел, когда
for (auto x: iota(4, 10))
сделает ровно то же самое, но более явно. Вам так жаль эти 4 знака? Или вам не угодило имя/неймспейс алгоритма? В питоне эта штука вообще называется xrange. А с неймспейсом вполне можно бороться (using std::ranges::view::iota;), тем более учитывая что в с++20 появились модули
Получается, «комитет» со своими кривыми view::iota противоречит своим же предыдущим решениям…
каким таким решениям?

Ну, давайте подумаем логически:

Человек хочет пробежаться в цикле от элемента a до элемента b.

Почему для этого недостаточно варианта for (i = a...b)?

Какая дополнительная польза от записи через view::iota? Ну то есть должен же быть смысл у этого синтаксического шума? (Или не?)

Только пожалуйста с аргументами, а то 20-летние синийоры, любят ставить минуса и убегать без пояснения :)

Ну, давайте подумаем логически:
Человек хочет пробежаться в цикле от элемента a до элемента b.
Почему для этого недостаточно варианта for (i = a...b)?
как минимум потому, что для контейнера cont настоящий код выглядел бы например так (и это еще при условии что b >= a):
for (i = min(a, cont.size())..min(b, cont.size()) {
    auto& e = cont[i];
    // ...
и тут экономия на пару букв уже не кажется такой уж существенной, верно? Особенно в сравнении с:
for (auto& e : cont | drop(a) | take(n))
Хотя постойте, во втором варианте же меньше букв?
Если компания по какой-то причине хочет использовать устаревшие технологии, то новый Стандарт ей никак не помешает. Даже при отдаче на аутсорс в качестве требований можно заявить компилируемость старой версией компилятора, ну или новой с флагом -std=c++03

Только всё равно не вижу особого смысла в этом… Плохой разработчик напишет спагетти и тарабарщину на любом языке. Хороший разработчик сможет на C++20 написать более понятный, простой, выразительный и, как следствие, более надёжный код, чем на C++17 засчёт новых возможностей языка.
Слушайте — вы садист или мазохист? Я что-то никак не пойму.

В-третьих, в B2B довольно распространена практика разработки «на заказ» аутсорсерами или консалтинговыми фирмами.
В бизнесе есть одно простое правило, нарушение которого выливается в, конце-концов, в крах: никогда не отдавайте в аутсорс ключевые компетенции.

Соотвественно: если вы отдаёте написание кода на аутсорс, значит качество этого кода для вас не очень-то и важно.

Так нафига вы заставляете людей мучиться с языком, который предназначен для написания максимально качественного кода профессионалами?

Используйте Java или Python, раз качество вас не волнует.

И наконец, а с чего Вы взяли, что профессионалы (имею в виду настоящих опытных разработчиков, а не «синьйоров в 25 лет после курсов питона») обязательно должны тупо бросать всё предыдущее и начинать радостно поддерживать «последний стандарт», просто потому что он вышел?
С того, что у C++ есть одна достаточна узкая ниша где он реально хорош: написание максимально эффективного кода профессионалами. И для неё — умение работать с последними стандартами очень важно.

Для других вещей есть другие языки. Тот уже Python уже давно поддерживается на микроконтроллерах и калькуляторах.

Нафига вам в таком раскладе мучить себя и других C++, если вы от него получаете одни проблемы?
С того, что у C++ есть одна достаточна узкая ниша где он реально хорош: написание максимально эффективного кода профессионалами. И для неё — умение работать с последними стандартами очень важно.


Как иногда говорил один наш преподаватель, «вы не понимаете, о чем вы говорите».
Для того, чтобы код был эффективным, недостаточно писать его по «последнему стандарту». Для этого, как минимум, нужно иметь эффективный компилятор, который бы генерировал максимально эффективный МАШИННЫЙ код.
А для компилятора чем проще ИСХОДНЫЙ код программы (например, «чистый С»), тем лучше он сможет выполнить оптимизацию МАШИННОГО. При чём тут последний стандарт?
А для компилятора чем проще ИСХОДНЫЙ код программы (например, «чистый С»), тем лучше он сможет выполнить оптимизацию МАШИННОГО. При чём тут последний стандарт?
разработчику с++ с 15-летним стажем стоило бы и знать что компилятор раскрывает шаблоны задолго до применения оптимизаций. Более того, чистый си во многих случаях может оптимизироваться хуже шаблонного кода на плюсах, который казалось бы делает то же самое, т.к. в с++ информация о типах не теряется. Простой пример — сишный qsort не сможет заинлайнить так же эффективно, как и плюсовый std::sort
А для компилятора чем проще ИСХОДНЫЙ код программы (например, «чистый С»), тем лучше он сможет выполнить оптимизацию МАШИННОГО.
Неверная посылка.
При чём тут последний стандарт?
Неверный вывод.

Последний стандарт тут притом, что он даёт вам возможность писать более эффективный код. C++11 добавили rvalue references, C++14 позволили больше операций производить во время компиляции, C++17 добавили inline переменные и mandatory RVO, C++20 — аттрибуты likely/unlikely, no_unique_address и поддержку asm в constexpr функциях (в комплекте с std::is_constant_evaluated, без которого в ней смысла нет)…

Хороший пример — это GNU ld, gold, lld.

Каждый из этих версий использует более сложный диалект, чем предыдущаа (GNU LD — C, GOLD — C++98, LLD — C++11), каждая содержит меньше исходного кода и работает быстрее.

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

В теории-то нет, конечно, можно любую программу написать оптимально как:
int main() {
  asm("… 100500 строк оптимального машинногго кода …");
}

Но на практике я видел либо эффективный код с использованием последних стандартов, либо неэффективный — написанный “в консервативном стиле”.

Возможен (и часто встречается на практике) ещё и вариант “последний визг C++ и дикие тормоза”, конечно.

А вот варианта “мы пишем только простой код, потому у нас всё хорошо”… не видел. Неизменно оказывается что единственное достаоинство у такого кода — возможно нанять дешёвых кодеров. И всё. А эффективностью там и не пахнет.

Но зачем, если вас это устраивает, вам сдался C++ вообще?

Возможен (и часто встречается на практике) ещё и вариант “последний визг C++ и дикие тормоза”, конечно.

Это, как правило, следствие бездумного паттернализма и шаблонного рака. И да, оно не редко. Как правило, замена на аналогичный "простой" код помогает на 120 %

А вот варианта “мы пишем только простой код, потому у нас всё хорошо”… не видел

Я вот считаю, что главный шаблон разработки - это KISS. Пока что он не подводил.

единственное достаоинство у такого кода — возможно нанять дешёвых кодеров. И всё. А эффективностью там и не пахнет.

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

Но зачем, если вас это устраивает, вам сдался C++ вообще?

Производительность. Другие альтернативы пока что или С, или какой нибудь Руст. Но с последним всё ещё хуже.

UFO just landed and posted this here

Ну превью версия студии полностью поддерживает c++20

Здорово! Они молодцы. С момента вебинара в феврале многое изменилось
Они неспроста помечены в стандартной библиотеке GCC как «экспериментальные». Прямо сейчас готовится десяток ломающих улучшений, Ranges активно дорабатываются и переделываются уже после релиза. Лучше ближайшие пару лет ими не пользоваться, если нет желания исправлять код с новым релизом компилятора.
Антон Полухин

А вот об этом можно поподробнее? Не успеваю за всеми новостями следить — общая картина не сложилась. А функционал ranges очень уж привлекателен.
UFO just landed and posted this here
iota это вообще греческая буква такая: ι
Видел дискуссии, что она означает в C++ (а это название идёт ещё из алгоритмов — std::iota для заполнения контейнера последовательными числами), но никакого внятного объяснения там не было. По некоторым данным название пришло в STL из языка APL.

По поводу namespace'ов — никто же не запрещает делать using namespace в своей области видимости. На то они и нужны)
По некоторым данным название пришло в STL из языка APL.
Только не «по некоторым данным», а об этом прямо написано в документации.

По поводу namespace'ов — никто же не запрещает делать using namespace в своей области видимости.
using namespace это настолько опасно, что лучше их вообще не использовать. И уж точно не стоит использовать using namespace std. Можете почитать в TOTW-153 почему.

Можно вытащить в свой неймспейс конкретно вещи, которые вам нужны.

P.S. К сожалению некоторые вещи были прямо заточены под using namespace, в частности std::swap. В этос случае нужно делать using namespace std в минимально возможной области видимости.
P.S. К сожалению некоторые вещи были прямо заточены под using namespace, в частности std::swap.
using std::swap;?
Речь идёт про шаблоны. Каждый тип должен определять свой swap, но добавить его в std нельзя.
Если вы используете using std::swap, то вырубите весь ADL нафиг и типы из какого-нибудь boostа перестанут работать.
Соответственно вызывать приходится как swap без префикса и без using std::swap.
Следовательно фактически единственным способом оказывается using namespace std; swap(a, b);.
Но как объясняет TOTW-153 делать это нужно в минимальной возможной области видимости.

Ну вот просто ещё одна проблема, которую когда-то не предвидели.

P.S. В TOW-99 про то, как подобные интерфейсы нужно делать чуть подробнее описано.
using std::swap; просто добавляет swap из std в область видимости текущего scope. Это не выключает ADL, более того, его обычно в ниблоидах и используют. Например, possible implementation of std::iter_swap.
В общем случае, к сожалению, не работает. Сравните foo и bar.

Хотя решение: делать через using std::swap, а идиотов, которые не соблюдают этикет выжигать калёным железом — тоже рабочий.
ADL всё-таки про поиск метода в неймспейсе аргумента, а не каком-то другом. Нужно ли вообще пытаться искать customization point в других namespace'ах? Имо нет.
Речь идёт про шаблоны. Каждый тип должен определять свой swap, но добавить его в std нельзя.
Если вы используете using std::swap, то вырубите весь ADL нафиг и типы из какого-нибудь boostа перестанут работать.

Что, простите?
Посыпаю голову пеплом. Как раз ADL остаётся, а вот обычный lookup вырубается.

Согласен, накосячил.
О! iota из D, похоже, взяли?
Хотя, там
оба варианта есть
    import std.stdio;

    // The following groups all produce the same output of:
    // 0 1 2 3 4

    foreach (i; 0 .. 5)
        writef("%s ", i);
    writeln();

    import std.range : iota;
    foreach (i; iota(0, 5))
        writef("%s ", i);
    writeln();

Кстати, в статье на нашёл, что у iota есть третий аргумент — шаг.

Если ты во вложенном цикле называешь переменные i,j то тебя уже ничто не спасёт в этой жизни. Автор бы ещё все переменные назвал а1, а2 ... , а потом на этом основании что то доказывал.

Sign up to leave a comment.