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, может быть можно было таки ещё немного его упростить, сделать как в питонеа зачем?
Может тогда уж сразу for i in 0..days
без всякой iota?
С одной стороны, фича конечно прикольная и нужная. С другой, в текущем варианте уже видна пачка проблем.
- Оператор
|
. Для его реализации требуется определённое шаблонное шаманство, пусть и не сильно сложное. Что менее приятно, для реализации своих рейнджей требуется определённая писанина. Увы, комитет не смог или не захотел внести оператор|>
или какой-то аналог. - Насколько я знаю, с поддержкой корутин всё непросто. Написать просто функцию с
yield
или аналогом, чтобы она сразу стала range-compliant, не выйдет. - Не совсем понятна ситуация с адаптированием своих контейнеров к поддержке рейнджей. До С++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>);
и реализовываете методы пока не скомпилится…Оператор новый вводить и менять 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 лет работы с с++ и до сих пор используете цикл со счетчиком вместо range based for? А продакшн кода я повидал достаточно, и enable_if'ы и прочее там весьма встречается.
Так о том же и речь, что for (auto i: y) намного лучше выглядит, чем вот эти вот view, iota, etc..
:)
Ну да, классический Design by Committee: https://ru.m.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%B8%D1%82%D0%B5%D1%82%D0%BE%D0%BC
Как часто бывает, правила задают одни, а пользоваться приходится другим.
В комитете нет "левых" людей все представители это самые активные разработчики c++ со всех стран мира. Вы говоритео о том о чем судя по всему даже представления не имеете, но "осуждаете". C++ это открытый стандарт любой может предложить свой пропозал от России есть рабочая группа 21 которая за вас может его предоставить на комитете.
Судя по тому, что они вкатывают в стандарт, их "активная разработка" только этим и ограничивается :)
К сожалению, единственный аргумент "сегодня" продолжать использовать с++ - это производительность скомпилированного кода. С каждым новым стандартом болото всё более дремучее :(
Ну это я так, для особых случаев. Вообще, новый Стандарт повышает выразительность, позволяет избавляться от трудночитаемых хаков. С ним проще писать легкочитаемый код, а спагетти и тарабарщину можно смастерить на чём угодно.
Но если честно, если кодеры в вашей компании не могут освоить последний Стандарт, то действительно стоит задуматься, нужен ли вам 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 (int i = a...b)
что было бы эквивалентно интервалу [a,b)
или более явно при необходимости:
for (auto x = [4...10])
Получается, «комитет» со своими кривыми view::iota противоречит своим же предыдущим решениям…
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-летние синийоры, любят ставить минуса и убегать без пояснения :)
Ну, давайте подумаем логически:как минимум потому, что для контейнера cont настоящий код выглядел бы например так (и это еще при условии что b >= a):
Человек хочет пробежаться в цикле от элемента a до элемента b.
Почему для этого недостаточно варианта for (i = a...b)?
for (i = min(a, cont.size())..min(b, cont.size()) {
auto& e = cont[i];
// ...
и тут экономия на пару букв уже не кажется такой уж существенной, верно? Особенно в сравнении с:for (auto& e : cont | drop(a) | take(n))
Хотя постойте, во втором варианте же меньше букв?Только всё равно не вижу особого смысла в этом… Плохой разработчик напишет спагетти и тарабарщину на любом языке. Хороший разработчик сможет на 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++ вообще?
Производительность. Другие альтернативы пока что или С, или какой нибудь Руст. Но с последним всё ещё хуже.
Ну превью версия студии полностью поддерживает c++20
Они неспроста помечены в стандартной библиотеке GCC как «экспериментальные». Прямо сейчас готовится десяток ломающих улучшений, Ranges активно дорабатываются и переделываются уже после релиза. Лучше ближайшие пару лет ими не пользоваться, если нет желания исправлять код с новым релизом компилятора.
Антон Полухин
А вот об этом можно поподробнее? Не успеваю за всеми новостями следить — общая картина не сложилась. А функционал ranges очень уж привлекателен.
Видел дискуссии, что она означает в 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 про то, как подобные интерфейсы нужно делать чуть подробнее описано.
foo
и bar
.Хотя решение: делать через
using std::swap
, а идиотов, которые не соблюдают этикет выжигать калёным железом — тоже рабочий.Речь идёт про шаблоны. Каждый тип должен определять свой swap, но добавить его в std нельзя.
Если вы используете using std::swap, то вырубите весь ADL нафиг и типы из какого-нибудь boostа перестанут работать.
Что, простите?
Хотя, там
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 ... , а потом на этом основании что то доказывал.
Стандарт C++20: обзор новых возможностей C++. Часть 4 «Ranges»