Комментарии 567
Переходите на C++Builder))
Простите, а это в каком году написано? Наезжать на инклюды и SFINAE после выхода C++20 странно. Я понимаю, что не все проекты могут себе позволить перейти на него, сам на 17м сижу, но это же не претензия к развитию языка.
Расширения обсуждались, только не в таком виде, а как унифицированный синтаксис вызова функций и методов, когда this уходит первым аргументом, как в D, но не пошло, а жаль.
Ну а properties бесплатные только когда инлайнятся, а если хочется бинарной совместимости, то нужен честный вызов метода, что не так уж дёшево.
Согласен, мой косяк в том что я не разобрался в концептах... если это решает проблему SFINAE, то с темой явно стоит ознакомиться не по-диагонали. Все же отмечу что SFINAE был, и остается с нами надолго. А такой класс решений, на мой взгляд, должен зарубаться еще на уровне идеи. Тем не менее это попало в стандарт.
Решают ли модули проблему инклюдов - не уверен. Опять же, не знаком с темой глубоко на уровне использования, лишь абстрактно. Однако, на мой взгляд, внутри одной библиотеки, коим разработчиком можешь являться, проблема остается все той же
SFINAE никуда не денется, он всё ещё используется и работает. Концепты не полностью заменяют, а решают 90% задач коротко и просто. Точно так же constexpr не решает все задачи шаблонов, только выделяет популярное подмножество и предлагает понятный способ решения.
Какие задачи решает SFINAE, но не решают концепты?
Не то чтобы совсем не решает, но лучше выглядит для тех, для которых SFINAE изначально придумывали. То есть простой разбор перегрузок без замороченных отличий по особенностям типов и enable_if. Например, перегрузки функции, обрабатывающей список значений, vector и map шаблонного типа. Гораздо проще написать 3 шаблонных функции-перегрузки с list, vector и map в сигнатуре, чем писать концепт is_list, is_vector, is_map.
А вот все использования enable_if и подобного уровня хаки концепты однозначно решают лучше.
Шаблонизация вместо перегрузок это частая практика, к сожалению.
У шаблонизации внутри может быть и enable if, и другие SFINAE техники, и концепты, и даже if constexpr, последнее особенно плохо читается, как по мне.
Мой поинт — всё, что раньше решалось через SFINAE сейчас можно решить через concept/requires. Не знаю контрпримеров.
Вы не любите кошек? Вы просто не умеете их готовить.
по поводу setter/getter который автор пытается использовать для работу с элементами вектора -вообще-то есть перегрузка операторов, которая в вашем конкретном примере сократит синтаксис.
Пользоваться или нет -решать вам.
Опять же чтобы инклуды не просились каждый раз, давно придумано
If defined module_included …
Это вроде азбука объявлений header файлов в С/С++.
Предположу, что речь была не про include guard (это само собой), а про парсенье инклудов в разных файлах в одном проекте. Если без precompiled headers, то одно и то же разбирается и компилируется снова и снова.
Недавно экспериментировал с precompiled headers в MSVC 2022 и пришёл к выводу что не зря их теперь там по умолчанию отключили. Сейчас студия предкомпилирует всё подряд и довольно неплохо отслеживает что и когда нужно перекомпилировать, а что нет. Ускорения от включения precompiled headers не заметил.
Заметил сильное замедление от закольцованных взаимных ссылок в заголовочных файлах. Оно иногда проявляется логическими ошибками, но не всегда. Зато явно увеличивает время компиляции и размер ipch файлов.
сорри, не понял про setter/getter для вектора... setter/getter у меня упоминается в пропертях, которые удобно их заворачивают, это не только к векторам и контейнерам относится, а ко всему
с инклюдами проблема в том что их все равно надо делать, а для уменьшения инклюдов все равно нужно писать forward declaration'ы. Это все тупая работа по решению зависимостей, которую в других языках перекладывают на кмпилятор
IMHO, чтобы пользоваться свежими фичами плюсов, нужно слишком много понимать даже не в языке, а в том, как это всё готовить.
На нашем небольшом open-source проекте код собирается внутри os, которую предоставляет вендор: raspberry pi OS, ubuntu для Jetson, debian для radxa. И там нафига не последние версии gcc или cmake.
Да, по хорошему надо самому собрать тулчеин со свежим компилятором и остальным - но это же реально хардкор, когда на сборку кода надо тратить столько же усилий, сколько на его написание
а какие альтернативы то на этих же платформах?
Если говорить про "как использовать свежий компилятор для debian в "полу-embedded" - то только собирать свой тулчейн для кросс компиляции. Или собирать свежий компилятор на самой sbc.
Опять же это такой нюанс экосистемы плюсов - по крайней мере я хз, как собрать одно приложение на плюсах в случае, когда разные части приложения нужно собрать под разные стандарты
не, я про альтернативу плюсам, в сборке тулчейна особо то проблем нет, crosstool-ng с этим очень сильно помогает.
Либо обрамлять старый код как статические либы, либо писать свою систему сборки, третьего не дано
Согласен, статья старая
Поставил плюс автору за старательность и минус за саму статью. Все, что вы написали неверно из-за одного момента. Вы не правильно понимаете:
Ты не платишь за то, чего не используешь
Если действительно разобраться, то это не совсем правда. Я не спорю, используя С++ ты довольно приближен к железу и накладные ресурсы довольно маленькие. Но они есть, от этого никуда не деться. В итоге разработчик вводится в некое заблуждение этой красивой фразой.
Это следует понимать, не как "накладные расходы на RTTI довольно маленькие, но они есть, от этого никуда не деться", а "если вам не требуется RTTI, то опция -fno-rtti полностью убирает даже минимальные накладные расходы и ненужный вам функционал".
Поэтому все остальные проблемы, которые вы перечислили в статье, не принципиальные и требуются только для обеспечения обратной совместимости кода (а не стандарта). Для того, чтобы легаси код можно было собрать новой версией компилятора, и все продолжало работало как и раньше.
А если вам не нужны или не нравятся какие-то возможности, ну так и не используйте их. Ведь "Ты не платишь за то, чего не используешь" :-)
Я не плюсовый разработчик, выскажу мысль со стороны.
В каких областях сейчас популярен С++ и какие у него в них были конкуренты в последние годы?
Мне вспоминаются такие:
Геймдев. Тут из конкурентов вроде только С# + Unity, и даже сам Unity частично вроде бы на С++. Конкурентов С++ почти нет, особенно для игровых движков.
Интерфейсы высокопроизводительных десктопных приложений. То что осталось на С++: браузеры, редакторы фото и видео с огромным циклом разработки. Что-то переписывать слишком долго переписывать, что-то вроде браузеров особо и не на что. Недавно появился Rust, но число разработчиков на нём несопоставимо, наработок меньше.
HFT - конкурентов особо нет.
Итого. У С++ долго не было особых конкурентов в его нише компилируемых языков без сборщика мусора и zero-cost абстракций. У большинства других языков конкуренты были. У Java был С#, а затем ещё и другие языки внутри экосистемы JVM. У Python - PHP, Ruby. Даже у формально безальтернативного JS были компилируемые в него языки в качестве конкурентов. Ну Rust появился, но у него и близко пока нет экосистемы С++.
Любопытно посмотерь поменятся ли что-то лет через 5-10 после появления Rust, а также всяких Zig, Carbon и прочих Mojo.
Помимо этих трех есть еще огромная индустрия эмбеддед (машины, ракеты, мед оборудование, спутники и тд.). Вот там плюсы вряд ли будут вытеснены в обозримом будущем
Да, спасибо что напомнили. Я ещё СУБД забыл, и там конкурентов тоже не было.
В эмбеддед в основном пишут на обычном Си.
Все-ж уже больше на ++. На старые контроллеры - да, на C в основном.
Rust ?....
Интерфейсы высокопроизводительных десктопных приложений.
Не совсем понял эту сентенцию. Если речь идёт о майнинге биткоина - то там в основном библиотеки для видеокарт. Если сложные математические расчёты типа лекарства или шахматы - то скорее что-то вроде чистого С с кусками на ассемблере, всё это с учётом архитектуры процессоров.
Сам интерфейс можно и на Питоне наваять, там скорость работы не требуется, главное скорость разработки и простота поддержка реализации.
Вообще я имел в виду всякие Photoshop, Blender, AutoCad, 3Ds Max, Adobe Premier.
Тут не только окошки, но и вся логика для их содержимого. Попробуйте браузер на питоне написать, вот тогда шутки про хром, жрущий всю память, перестанут быть шутками.
Биткоин с 2013 майнят только на асиках, потому что видюхи проигрывают им на порядки по производительности и энергоэффективности. Что ни говори, а C++ развивается быстрее хотя бы «биткоин-критиков», уже неплохо!
Конкурентов С++ почти нет, особенно для игровых движков.
Но авторы игр в большинстве своём пишут не игровой движок, а игровую логику для этого движка ;-) В тех же играх на Unity основной язык разработки именно С#
Большинство крупных игр пишется либо на внутреннем движке либо на анриле которые поголовно на С++ юнити удел Индии и маленьких проектов, за рядом исключений
Ну, вроде как, 7 проходный, а болячки когда объекты ссылаются друг на друга лечить никто не собирается, мол, колхозьте форварды как последние 40 лет
Любопытно посмотерь поменятся ли что-то лет через 5-10 после появления Rust
<...> первая стабильная версия (1.0) вышла 15 мая 2015 года <...> (Вики).
То есть ему уже 9 лет.
Первая стабильная версия Golang вышла в 2009, но никто на нём всерьёз не писал до примерно 2015г. Версия 1.0 у Python — 1994.
Субьективно с очень хорошим пиаром как у Golang и C на языке начинают массово писать в прод лет так через 4-5. А обычно — лет через 8-10.
Первая стабильная версия Golang вышла в 2009, но никто на нём всерьёз не писал до примерно 2015г.
В 2009-ом Гугл просто предъявил Golang миру, релиз версии 1.0 у Golang-а состоялся в марте 2012-го.
Собственно, про Rust так же было известно задолго до релиза версии 1.0 в мае 2015-го.
Если верить Wikipedia, то Docker появился в 2013-ом, а в 2014-ом там заменили LXC на написанный на Go libcontainer:
Docker debuted to the public in Santa Clara at PyCon in 2013.[48] It was released as open-source in March 2013.[21] At the time, it used LXC as its default execution environment. One year later, with the release of version 0.9, Docker replaced LXC with its own component, libcontainer, which was written in the Go programming language.
Так что с "никто" и "всерьёз не" получается как с совой на глобус.
Ну и добавьте к дате релиза Go 1.0 девять лет и оцените насколько сильно он проник в мейнстрим к тому времени. И сравните с проникновением в этот же мейнстрим Rust-а на данный момент. Как раз 9 лет с релиза версии 1.0.
Тут смотря что считать мейнстримом. Например в области микроконтроллеров без библиотек вендоров (HAL) редко кто пишет. И пару лет назад они начали появляться на Rust.
Но кроме того на Rust есть стандартное API HAL (https://github.com/rust-embedded/embedded-hal), чего за все цать лет не удалось создать в других языках.
Фактически под все микроконтроллеры есть библиотеки на С, сильно меньше половины на С++, и сейчас примерно столько же на Rust. Я считаю что это уже мейнстрим.
По вашим критериям так ни C#, ни JavaScript до мейнстрима так и не добрались.
В микроконтроллерах конечно. Вероятно и не доберутся, т.к. сборщик мусора и рантайм.
Просто я считаю, что Rust лучше всего подходит для микроконтроллеров, ядра ОС и драйверов. Вобщем тех областей, где нет обычного рантайма, который дает ОС во многих других языках.
Кстати, в ядро Linux тоже Rust добавили, и на нем пишут новые драйвера. Так что в 1,5 из этих 3 областей уже мейнстрим. А обычные приложения под Linux, Windows или Android лучше писать на чем-нибудь со сборщиком мусора (имхо).
В микроконтроллерах конечно.
Прекрасно. Только к контексту разговора имеет опосредованное отношение.
Чтобы было лучше понятно, такая аналогия:
В конце 1990-х JavaScript был мейнстримом в нише Web-страничек. Но на всю разработку софта (вне зависимости от прикладных ниш) влияния не оказывал. Прошло двадцать лет и JavaScript мейнстрим из мейнстримов вне зависимости от ниши.
Может быть Rust стал мейнстримом в разработке для микроконтролеров. Прекрасно, если так.
Но вот вообще в разработке софта вообще разговоров о Rust гораздо больше, чем самого Rust-а. Не смотря на то, что релиз версии 1.0 состоялся девять лет назад.
Путь к настоящему мейнстриму у тех же C++, Java, C# и Go был гораздо короче.
А может вы просто не хотите оглянуться по сторонам?
Cloudflare целиком на Rust переписались, в исходниках Android его уже столько же, сколько C, AWS несколько сервисов целиком переписали и не останавливаются (и сделали Firecracker целиком на нем, опенсорс менеджер микровиртуалок, на котором работают Lambda и Fargate), с голенга многие тоже переписываются. Я могу дать полный список известных мне проектов, там от MESA до Figma, Vercel и части бэка npm.js репозитория. И под спутники пишут на нем, и новые проекты от мала до велика (от Tauri, который по сути Electron здорового человека, и Embassy, который embedded просто и быстро, до Western Digital, которые платформу обработки данных на 5 петабайт в день на нем пилят). Но возможности спрятать под спойлер я тут не вижу, а еще больше засорять коммент не хочется.
https://habr.com/ru/articles/813645/comments/#comment_26837079
"без сборщика мусора" - есть полумеры в виде ARC (obj-c, swift, vala). есть @nogc
в D
Достаточно открыть vcpkg.io и ознакомиться с огромным количеством библиотек написанных на C/CPP, чтоб увидеть - 90% современной базы кода основана на указанных языках. Все остальные языки и фреймворк не переписывают ни одну базовую библиотеку но почти все в той или иной мере через биндинги и прочие приколы завязаны на них. Таким образом место CPP отнюдь не только в игровых движках/десктопах/ембедед. Это фундамент на котором строится если не все, то львиная доля остального.
ПС. Извините, сразу дополню коммент мнением по самой статье, раз уж начал ?.
Ну вы бы блин ещё к фортрану и коболу докопались по поводу синтаксиса/стиля итд. CPP занимает свою важную нишу и имеет преогромную кодовую базу(пример выше), вносить изменения в язык нужно крайне осторожно.
Долго хидеры компилятся? Ну дайте больше потоков, юзайте jom коли студийный nmake по сей день в одно ядро долбиться. Используйте cmake+ninja итд. Это вообще не проблема уже, а время разработчиков языка можно потратить на более важные проблемы.
Не читаемые ошибки и тупые классические IDE, так не проблема языка - это MS и Ко должны позаботиться.
Если среда разработки лагает - переходите на Qt Creator или VS Code и CMake, никаких лагов, кроссплатформенность, менеджмент зависимостей из vcpkg, простота и лёгкость из коробки.
Если сам язык не устраивает или не подходит под какую-то задачу, ну так возьмите другой язык - их вон наклепали гору куда лучше под конкретные задачи. Правда там любой поскреби - рано или поздно доскребешься до C/CPP или библиотек на них.
То как выглядят лямбды, итераторы, стандартная библиотека - ну вот так вот исторически сложилось, такова цена свободы и универсальности языка, да есть определенные нюансы. Ну так зато к примеру у вас лямбда это +/- обычный CPP объект (ну функтор), только безымянный и в compile time созданный компилятором, потому и синтаксис такой специфический.
Ну да надо лямбде перечислить что нужно захватить или просто захватить все, но где тут проблема то? Вы хотите чтоб решение принималось за вас? А как вы можете рассказать? Какой-то может есть универсальный или эвристический алгоритм который поймет что нужно а что нет? А это повлияет на рантайм или на компайлтайм?
Тут чего не коснись - может так в ответ по башке прилететь всему сообществу - десятилетиями аукаться будет. Многое из сказанного в статье это вкусовщина. Мне вот например нравится как многое сделано.
Уж пусть лучше CPP развивается медленно но предсказуемо. Вы смотрите вокруг на примеры. Питон с 2 на 3 версию сколько уже, 15 лет до конца никак не перейдет? До сих пор тянут лямку с питоном 2.7, все никак не закопают и в линуксе до сих пор жонглировать приходится чтоб то один то другой скрипт запустить то во 2 то в 3 питоне. А тоже были светлые идеи типа "давайте-ка за старое не будем цепляться". Надо оно в языке который вместе с C является базой для чуть не всего ИТ? Не думаю.
ППС: тем не менее спасибо за статью, было интересно, прошу прощения за излишнюю эмоциональность в изложении мнения/ответа ?. Предлагаю сконцентрироваться на плюсах которые вокруг языка появились и развиваются в верную сторону, типа стандартизации проектов с CMake/CMakePresets, с нормальными менеджерами зависимостей типа vcpkg/conan итд. Может сам язык и не сильно развивается, но инфраструктура вокруг него разительно улучшилась в последние 5 лет.
Есть что ответить на пару ваших тезисов.
Пишу на .NET. Понятно, что рантайм на плюсах, но пакетов/библиотек, которые опираются на нативные(сишные) либы - крайне мало. Ту же реализацию grpc майкрософт переписали на .Net.
По поводу инфраструктуры вокруг плюсов - да, развивается, но отстаёт лет на 10. Не так давно я писал маленькое тестовое приложение. К нему нужно было подключить две библиотеки. Одна из них была только в conan, другая только в vcpkg. Забил на всё на этапе заставить нормально работать vcpkg в студии.
Ну как бы что-то движется, но после других языков тулинг плюсов - боль и страдания
Отлично что появляются нативные версии библиотек под каждый язык. Будет работа у разработчиков, будут платить зарплату на поддержку данной реализации под конкретный язык.
Тем не менее, я не уверен что кодировщики (ffmpeg), архиваторы (любой хоть zlib), мат-либы (NumPy) и многи другие вообще хоть когда-то будут переписывать под какой-то иной язык. C и в куда меньшей мере CPP здесь вне конкуренции.
То что отсутствует библиотека в vcpkg, ну так мы же должны на дистанции смотреть с конкретными примерами. По vcpkg, когда они разобрались с базовой архитектурой своего пакетного менеджера (а там переделок хватало в первые три года), то они взяли норм темп на добавление новых либ. Вот здесь страница с релизами vcpkg, https://github.com/microsoft/vcpkg/releases, обратите внимание что в каждом релизе указано кол-во обновленных и добавленных библиотек.
Релизные срезы пошли каждый месяц и ежемесячно добавляется по 5-10-20 библиотек. А обновляется по 100-200. Это отличный уровень поддержки я уверен как от сообщества так и от множества коммерческих компаний. Пройдет немного времени и будет там все Вам необходимое.
Обратите внимание что порт не добавить и не обновить если не пройдет внутренний CI/CD на всех поддерживаемых платформах а также глубокий ревью - это указывает на очень хорошие контроль качества и регрессий.
Да даже свой порт сделать очень просто, в несколько строчек (если исходная либа на CMake), вот взгляните на пару моих примеров: https://github.com/microsoft/vcpkg/pull/34510
И на этом фоне давайте сравним термин "тулинг плюсов" 10 лет назад со сборкой всего самостоятельно километровыми скриптами/руками под нужный компилятор и сейчас с пакетными менеджерами. Ну и наконец - другие языки моложе, в них учтено множество ошибок, а многие из них и не компилируемые вовсе, там от пакетного менеджера требуется не собрать мудреную библиотеку с десятком зависимостей разными компиляторами под разные платформы, а тупо скачать несколько файлов и разложить по папочкам. Уровень сложности решаемых задач даже нельзя сопоставлять.
Ну короче, это все лирика, я просто за позитивный настрой =)
Вот честно, не понимаю ажиотажа вокруг Rust.
ИМХО, похоже на попытку выделиться, плюс создать язык для тех, кто "не осилил С++".
Вот какой был смысл придумывать для него такой корявый синтаксис, который ближе к ассемблеру, чем к ЯП "высокого" уровня?
Чтобы хоть в чем-то "порог вхождения" повысить, что ли?
Мол, "вот вам, гадкие С++ники, хрен легко перестроитесь".
Естественно С-подобный. Хотя бы потому, что на нем основано куда больше языков, и соотвественно, кода, чем на Rust-подобном.
И можно не впадать в демагогию, а-ля "Не существует людей, осиливших C++"?
Как написали ниже - "осилить c++ в мере, позволяющей написать +- адекватное большое приложение легче чем осилить ржавого ".
То, что кто-то не осознает без помощи компилятора, что такое многопоточность и асинхронность - это наверное его проблемы?
а в проектах на других языках ошибки ни когда не совершают и все они отлавливаются на этапе запуска у программиста в ide, я правильно понял мысль?
Лучше когда компилятор отлавливает больше ошибок, чем меньше. Вы же не будете с этим спорить?
Как говорится "это зависит". Тут недавно была статья про Rust в геймдеве, так автор очень сильно плакался о том, что т.к. Rust очень хочет выловить максимум возможных ошибок, то он требует делать слишком много телодвижений в случае некоторых изменений, когда программисту нужно "побыстрому запрототипировать".
Т.е. в тот момент когда "лучше возможно с ошибками, но быстренько попробовать", Rust говорит, типа "нет милок, ты захотел сделать вот так, тогда давай зарефактори мне половину кода, чтобы я был точно уверен что ты нигде не накосячил".
В той статье автор не понятно на что плачет. Всегда можно написать static mut переменную, и везде к ней обращаться через unsafe. Также можно всю обработку ошибок игнорировать, вызывая unwrap, для прототипа сойдет. Всё обвешать слабыми ссылками и т.д. Но он хочет писать на Rust как на Python, о чем ему в комментариях и написали.
Остальные его претензии относятся к геймдев библиотекам. Ну так они видимо сырые, и когда-нибудь доведут до ума. Те же проблемы будут у любого языка, который только пришел в геймдев.
Отнюдь, осилить С++ можно, я потратил 2 года свободного времени чтоб разобраться в почти всех тонкостях языка. Но есть нюанс, я вот понимаю что при передаче классической строки в функцию с аргументом типа char*& оно может быть ub на старых стандартах и наоборот материализоваться на новых, но я всё ещё junior с ЗП меньше 80К
А мог бы писать говнокод все эти 2 года и дослужиться до мидла с окладом в +150КК
ЗЫ я про код вида Do("hello"), где Do это void()(char*&)
чтоб разобраться в почти всех тонкостях языка
Прям разобраться? Вот прям читая произвольный код можете объяснить, как будет происходить (C)TAD? pointer provenance вам хорошо понятен? Да хотя бы даже категории значений?
А то я смотрю на комитет, там люди десятилетиями над языком работают, а потом принимают дефект-репорты о таких фундаментальных и, сравнительно, простых вещах как объектная модель, например.
Уу.. а как это откомпилилось? Должно быть `(const char*&) - не тот тип, это не UB. Это в сях можно было.
что то ты загнул конечно))
я на плюсах уже 10 год пишу, 2 из которых в статусе lead и вроде не плохо понимаю за многопоточку, SFINAE, макросы и кроссплатформенного кода пишу много, но даже я бы не стал зарекаться про "во всех тонкостях", почти на каждом докладе по C++ я всё ещё узнаю новые для себя вещи за что и люблю этот язык, он безграничен в своём познании
Сейчас(3 года как) выкатили корутины с концептами и вроде в общем оно конечно понятно зачем и как но я не готов прям сходу что то написать с их использованием.
А ещё есть атрибуты которые аж в 11 появились но как таковые редко используются по крайней мере у нас, не говоря уж про memory_order в атомарных операциях, я честно за все время пару человек только встречал которые понимали как этим правильно пользоваться.
Так что ты либо гений либо находишься на другом пике кривой Даннинга-Крюгера
Я сейчас работаю и у меня есть коллеги с большим опытом. Мне кажется разница в том что я изучаю язык просто ради веселья, а не для того что бы решать какие-то реальные задачи. Мне крайне весело разбираться в системе типов С++ и то как там это всё работает. Решать всякие задачки которые ломают мозг, просто ради того чтоб их решить. И в итоге я использую те же концепты-замыкания постоянно, ибо для меня это очевидная фича языка которая позволяет работать с типами более гибко, а для моего коллеги это лишь способ решить проблему с перегрузкой какой-то шаблонной функции.
И если честно, я жалею что потратил так много времени просто ради того чтоб только это и изучить. Я бы мог устроится куда-то как стажёр и получить больше опыта, больше знаний фреймворков, большую зарплату, а в итоге за эти +2 года только С++ и знаю.
Имхо, осилить c++ в мере, позволяющей написать +- адекватное большое приложение легче чем осилить ржавого :)
Многие из идей раста близки современному C++. Небезопасные операции в которых легко совершить ошибку спрятаны в реализации, но в Rust значительно больше гарантий.
Но объем этих гарантий делает написание кода с супер высокими требованиями по производительности/задержке или с доступом к железу очень неудобным или невозможным без широкого использования unsafe. Вроде бы сообщество Rust планирует улучшить эргономику unsafe, но пока что некоторые вещи значительно удобнее делать на C++.
Так что Rust это конкурент C++ когда безопасность важнее производительности и конкурент языков со сборщиком мусора когда производительность важнее скорости разработки.
когда безопасность важнее производительности
Но ведь безопасность как минимум в значительной степени равна корректности работы. Отсюда вопрос: зачем вам быстрые некорректные программы?
Rust для обеспечения безопасности и отсутствия UB вводит ограничения, начиная от проверки индексов при доступе к элементам массивов, до необходимости использования Arc<Mutex<T>> в многопоточных приложениях.
В большинстве случаев эти ограничения не важны, или обходятся, но, например, многопоточный мутабельный доступ к памяти может быть необходим.
Безопасность памяти не гарантирует корректность. Кроме того распространенность языков с динамической типизацией показывает что скорость написания может быть важнее безопасности типов. Можно задать вопрос: зачем нужны быстро написанные некорректные программы?
если кратко, нет, не равна.
Раст - С++, непохожий на С++, чтобы не вспугнуть Линуса
изменить стратегию компиляции? Решение буквально очевидно
расскажете "очевидное" решение? Стратегия компиляции из С проста и гениальна, у неё куча преимуществ. Я так понимаю автор предлагает какой то "глобальный неймспейс" в котором, видимо, весь С++ код мира собран, иначе откуда компилятор поймёт что там есть неизвестно
Проблемы со специализациями решаются элементарным правилом, не делать специализации хрен знает где в .cpp файле, а делать либо в том файле где объявлен тип, либо в том где объявлено то что специализируют
Нет, зачем весь код С++ мира, только локального проекта. Грубо говоря сначала все распарсить, собрать AST. Решить все зависимости. А потом уже генерить код. Можно представить это как некую аналогию precompiled header'а на весь проект и forward declaration всех типов
и откуда компилятор узнает кто у вас составляет "локальный проект"? А про хедеры подключаемые несколько раз что он скажет? Что будет с инкрементальной компиляцией? И почему вы при таком подходе не сделаете unity сборку, которая соберёт все .cpp в один файл?
Система сборки про это расскажет.
эх если бы вы знали, что наоборот система сборки спрашивает компилятор что является зависимостями проекта. Иначе, например, поведение языка зависело бы от системы сборки, а не от компилятора
Ворнинг скажет, что модуль импортируется дважды, и второй импорт не нужен
а он нужен, не все хедеры pragma once, у них гораздо больше применений
То же, что сейчас в языках с более-менее нормальной модульной системой.
и что же там? Ах да, скриптовые языки там, вот что. Либо всё очень плохо с инкрементальностью
Потому что тогда и инкрементальной компиляции не будет, и с анонимными неймспейсами надо быть сильно аккуратнее.
в вашей системе тоже не будет, юнити сбокра нужна для одноразового билда, а уж про неймспейсы с вашей системой "глобальности" всех имён среди всех хедеров и говорить нечего
Ему про модули говоришь, а он всё в терминах хедеров и текстового препроцессора думает.
в статье чётко написано, "Глобальный неймспейс" "непересекающиеся имена, что вам что их написать что ли"
ill-formed, no diagnostic required.
в условиях одного TU эта проблема как раз исчезает, т.к. компилятор может диагностировать всё
то тут где вопрос компилятору?
всё что вы перечислили это команды для нахождения каких то файлов, которые к языку никакого отношения не имеют. Уже потом билд система спрашивает у компилятора какие зависимости у конкретного .cpp файла
Куча NDR описана именно так потому, что их надёжная диагностика (в смысле decision problem) эквивалентна проблеме останова.
куча связана с тем что есть независимые TU, немного связано с формальностями в шаблонах, вот и всё
Даже NDR'ность бесконечного цикла без сайд-эффектов
это не IFNDR, а undefined behavior
IF/NDR - лексически неверный код. UB - семантически неопределённый код. Программа не может быть IF/NDR .потому что ее нет. Есть какой-то непонятный артефакт (если есть).
я немного потеря нить разговора но как минимум то что a * a * a + b * b * b
выходит за пределы используемого типа и итерироваться уже можешь не до a <
UINT_MAX
а до того когда всё выражение будет a * a * a + b * b * b
меньше UINT_MAX
, как это гарантировать не задача компилятора и ни когда не должна ей быть
Это не IFNDR, IFNDR назначается явно. Например, достижение точки выхода из функции, не возвращающей void, без return - IFNDR.
[intro.progress] не определяет такой ситуации применительно к циклу while (for и do-while наследуют поведение while), поэтому она не определена "by omission". Это UB, в связи с проблемой найти побочные эффекты и точку выхода, что возможно только при соблюдении требований в [intro.progress].
Причём UB в ряде случаев может случиться и в компиляторе. Он попытаться развернуть ваш бесконечный цикл, считая что из него можно выудить константный результат и получится что-то... или программа (компилятор) упадет.
Учтивая то, с чем программы линкуются. то это как минимум - весь код ядра, компилятора, библиотеки времени исполнения и всех библиотек. А дальше возникает проблема о которой большинство программистов на языках "высокого" уровня не задумывается.. Как быть с поздним связыванием? Как быть с совместимостью? И на чем компилировать. Даже сейчас компиляторы вроде gcc или Майкрософта могут исчерпать память на ОДНОМ файле в некоторых проектах. Глобальное , однородное, неупорядоченное пространство имеет в основном возможность существовать только в системах похожих на виртуальные машины, и то только в пределах одной "сборки". Т.к. обычно эти системы - Java, C-шарп все-таки имеют какое-то позднее связывание.
Позднее связывание подразумевает упорядочивание определений, и все те же проблемы нарушения ODR появляются на уровне "библиотека А использовала версию Х модуля Б, а библиотека Ж использовала версию Y модуля Б".
C++20 modules примерно это и реализуют.
Их долго делали как раз отчасти потому, что необходимо было обеспечить взаимодействие системы сборки и компилятора, когда компилятор сначала анализирует файлы чтобы составить список зависимостей, чтобы потом обеспечить сборку.
В стандарте вроде же нет такой штуки, как система сборки. На сколько я помню историю тикета про модули в репе cmake, то там отдельные реализации для gcc и clang ибо компиляторы отдают списки зависимостей в разных форматах.
То есть я про то, что модули в плюсах - фича в себе. Нельзя взять произвольную систему сборки с поддержкой модулей и произвольный компилятор с поддержкой модулей
Я правильно понимаю, что p1689 ещё не принят? Там вроде pending стоит у последней ревизии.
С мой колокольни история с модулями выглядит так:
Комитет: мы тут придумали модули на все случаи жизни, но нас совершенно не колышит, смогут ли компиляторы это реализовать. Системы сборки? Какие системы сборки, мы пишем стандарт и не знаем ничего о системах сборки.
Разработчики cmake: issue про поддержку модулей была открыта 5 лет назад, пора бы уже хоть какую-то реализацию сделать. Ну и заодно p1689 напишем
Спросите у стариков сколько времени собирался проект на Delphi. И воспользуйтесь мудростью якобы турецкой поговорки
Не важно как далеко ты ушёл по неверной дороге, возвращайся назад.
А вопрос о том почему во всех сколь бы то ни было популярных языках есть show stopper, он же deal breaker, есть конспирология. А вопрос почему когда его маловато, как в случае Julia которая конспирологически не просто так, демонстративно но по итогу не вполне удачно, открещивалась от самой идеи создания "приложений", регулярно проводятся медийные кампании - тоже коспирология.
В старое время, когда Java вдруг начала набирать популяроость, о разработчиках было сказано
Они бегут не на Java, они бегут от Win32
А сейчас набирает популярность Rust...
Спросите у стариков сколько времени собирался проект на Delphi.
Довольно шустро. Но сейчас я пишу в основном на C++ (реже Phyton и старое C)....
Хотя Delphi (Visual Pascal) стоит.
А сейчас набирает популярность Rust...
Ну вот часть разработок буду делать уже на нем. И не вижу особой проблемы в переходах.
В дельфях не просто шустро -- иногда в сотни раз шустрей, чем на сях... Плюс там достаточно надёжная типизация (и отсутствие надёжности и си), и модули, а не дурные инклуды... Собсно, главный недостаток -- что пошли по пути копирования функционала жабы/сишарпа, а не с++ (убогие генерики вместо полноценных шаблонов, например).
Как человек, периодически занимающийся реверс-инжинирингом, я считаю "полноценные шаблоны" одним из самых вредоносных функционалов C++. Когда ты в пределах одной DLL или EXE встречаешь уже пятую реализацию одного и того же класса-шаблона с разными параметрами, то можно слегка озвереть.
На вкус и цвет, как говорится. На мой взгляд, пусть шаблоны и гибче, дженерики на порядок безопаснее и как итог - практичнее. Да и дженерики можно по-разному реализовать. В FPC, например, дженерики больше похожи на шаблоны, хотя остаются дженериками.
Банальное добавление элемента в вектор ведь тоже несет некие затраты? Тем более если происходит реаллокация, а она может быть уууух какая...
+100
Лично для меня еще существенным минусом является вынесение общих функций наружу, вместо использования их как функций‑членов класса. Мне, например, гораздо проще и очевиднее вызвать myVector.find_if()
через точку, чем вызывать ее как внешнюю функцию с передачей итераторов внутрь.
Общие алгоритмы работают для любых контейнеров, в том числе и самописных, если их итераторы удовлетворяют явным требованиям и инвариантам STL. Это избавляет от необходимости писать весь этот код для самописных контейнеров, при этом контейнеры могут не знать о существовании стд алгоритмов впринципе.
SFINAE
Just use c++20 and requires
А ведь эти проблемы можно было бы решить, если бы умные указатели работали на уровне языка?
Нельзя.
Рассмотрим пример - dynamic_cast<>
и RTTI (runtime type information). Это отключаемая фича в С++, но по-дефолту она включена и многими используется. Многими программистами С++ она воспринимается как бесплатная, однако при включении RTTI постоянно происходит скрытая работа в рантайме.
Скрытая она только для джавистов. Если говорить про наличие typeid и прочей мишуры в бинарнике - то это копейки на фоне всего остального, что обычно зашивается в бинарник в том же гейдеве.
Лямды
Вся портянка, написанная здесь - это буквально, в шарпах менее понятный синтаксис, подходящий в очень узких кейсах и не подходящих в других. Лямбды в плюсах позволяют писать матчеры, compile-time обходы, триллион всего, имеют контроль над лайфтаймами, то, что они захватывают, что они принимают как аргумент и что возвращают. Ну да, это приходится писать, хотя язык уже позволяет, например, опускать (), если они пустые. Проблема-то где?
IDE
Нытье про MSVC/Xcode - это просто нечто. Just use vscode, clangd + пачка плагинов = нелагучее и достаточно эффективная IDE. Дебаггер только чутка тупой, но если сильно приспичит много дебажить, то можно и CLion запустить. Clion Nova так ещё менее лагучаяя, может на неё все переедут.
Важно не то, что зашивается в бинарник, а то, что засоряет кэши.
Какие кеши? RTTI не попадёт в кеш, если он не используется.(и это я ещё GADT не брал в расчёт), и тогда можно будет обсудить, какие матчеры они позволяют писать.
Ещё бы Хаскелл не был уродским и неудобным во всём остальном)В других языках есть просто нормальный компилтайм-язык, а не вот эти кусочки, разбавленные бочкой известно какой субстанции
В каких?Не «имеют контроль», а «требуют от программиста контролировать». Это слегка разные вещи, я бы даже сказал, противоположные.
Где?
Процессорные. typeinfo достаточно часто кладётся компилятором прямо рядом с vtbl, так что попадает и засоряет, если vtbl используется. А там и имя типа, например, есть, которое может занимать килобайты в случае какой-нибудь темплейтной хренотени.
интересное у вас понимание кешей, наверное процессор сидит и думает, ну вот втейбл же, наверное надо и имя загрузить, целиком, ну на всякий случай
хаскеле в виде TH
В хрусте
В языках с QTT в 0-аннотированном фрагменте
какое потрясающее перечисление широкоиспользуемых языков
да не нельзя, потому как там где нет возможности использовать современный стандарт C++ то и другой язык использовать там не получится так что когда мы говорим про преимущества перехода на "другой" язык то и сравнивать нужно с последним стандартом C++
а каким образом эти портянки старого кода с вашим кодом на хаскеле тогда взаимодействовали и что мешало тоже самое делать с кодом написаном на новой версии компилятора, да и зачем там что то было переписывать, я вот буквально на днях апгрейдил тулчейны для кода 30 летней давности, все что потребовалось это добавить пару опций компиляции для игнорирований новых предупреждений и добавить const в паре мест для char*. Я не пытаюсь докопаться, просто для меня это максимально странно выглядит.
В другой компании нельзя было использовать последние компиляторы из-за несовместимости ABI с поставщиком закрытой библиотеки для парсинга сообщений от биржи, тогда как на хаскеле парсер пишется за пару вечеров.
не будем про то что поставлять проприетарные либы с C++ API это моветон, но хотелось бы уточнить, правильно ли я понимаю, вместо того чтобы написать тот же парсер на каком ни будь ragel или re2c и работать с ним в той же самой "экосистеме" и не заводить новых языков было решено переписать его на haskell? Тут явно нужно знать больше контекста потому что в отрыве это какой то странный мув безумного хаскелиста...
втейбл и RTTI не находятся в объекте, там только указатель на первый. Да, вот этот указатель и есть вся нагрузка... И это не индивидуализированная информация, мы не в LUA где виртуальные таблицы на ходу можно редактировать.
И так как там успехи с аналогичными матчерами, чтобы не делать гроздья вложенных std::visit (в которых fallthrough всё равно невыразим)?
[](auto&&){}Что «где»? Контролировать где? Там, где язык не даёт никаких гарантий, будет ли жить то, что вы захватили по ссылке, всё время жизни лямбды.
Ты понимаешь, что любая гарантия - это оверхед? Почему кто-то тебе что-то должен гарантировать? Хочешь гарантировать - гарантируй: юзай шареды, интрузивы, что угодно. Не хочешь - не юзай. Лямбда - это языковой механизм.
Можете развернуть мысль?
предполагаю это относилось к "которых fallthrough всё равно невыразим)", что как раз таки и является обработкой fallthrough
Только не всегда это рантайм-оверхед.
Всегда. Тот факт, что вы думаете, что его нет - не означает, что его нет.
И да, отсутствие гарантий — тоже оверхед, просто на программиста.
Бла-бла-бла. С таким же успехом можно юзать гараж коллектор.А можно мне как-то семантика языка будет это гарантировать во время компиляции, а не рантайм-механизмы во время выполнения? Я не хочу платить за шареды, интрузивы и другие красивые слова.
Язык видимо должен запретить создание ситуаций, в которых компилятор понятия не имеет, есть ли гарантия, что кто-то проживёт долго. Никаких гарантий во время компиляций просто не существует - это просто маркетинг.
Если функция принимает ссылку на struct Foo}туда может прийти часть области памяти класса Boo, унаследованного от Foo, или класса, содержащего Foo, или настоящий Foo, не так ли. Но без RTTI - это задача программиста проследить чтобы функция не пыталась выйти за границы и сделать upcast неправильно. Однако RTTI содержится не во всех классах.
ламда вставленная в шаблон далеко не всегда оверхэд, часто наоборот. Практика показала что компиляторы вмонтируют ее в код алгоритма. Что может не происходить с функциями, т.к. явно берётся адрес функции. Я уже не говорю о bind, или std::function, там вообще виртуальная диспетчеризация.
Один коллега наоборот жаловался что он не может отлаживать "эти смайлики", отладчик в них "не заходит". Исследование показало, что мелкософт по умолчанию включает function inlining в отладке, его надо было выключить.
Ещё бы Хаскелл не был уродским и неудобным во всём остальном)
По-моему один из самых красивых языков. Раскройте в чём уродство, пожалуйста
Покажите матчер, который делает что-то, эквивалентное
А та часть матчера, которая по значениям, она какая-то особенная, или просто сахар для if-else'ов?
Да миллион способов написать тоже самое на C++ вот первые пришедшие в голову, да оно чуть более многословное но зато гораздо эффективнее, 4 вариант вообще инлайнит строчку вместо всех вызовов и сравним это с тем что нам нагенерит ghc https://gcc.godbolt.org/z/Md4xEqcdh
Hidden text
struct B1 {};
struct B2 {};
using Bar = std::variant<B1, B2>;
struct C1 {
int v;
};
struct C2 {
Bar bar;
};
using Foo = std::variant<C1, C2>;
namespace foo1 {
template<class... T>
std::string_view to_string(const std::variant<T...>& variant);
std::string_view to_string(const B1&) {
return "cb1";
}
std::string_view to_string(const B2&) {
return "cb2";
}
std::string_view to_string(const C1& c1) {
if (c1.v == 0) {
return "c1 zero";
}
if (c1.v > 0) {
return "c1 pos";
}
return "c1 neg";
}
std::string_view to_string(const C2& c2) {
return to_string(c2.bar);
}
template<class... T>
std::string_view to_string(const std::variant<T...>& variant) {
return std::visit([](auto&& arg) { return to_string(arg); }, variant);
}
} // namespace foo1
namespace foo2 {
template<class... Args>
std::string_view to_string(const std::variant<Args...>& variant) {
auto constexpr visitor = []<typename T>(T&& arg) -> std::string_view {
if constexpr (std::is_same_v<T, C1>) {
if (std::get<C1>(arg).v == 0) {
return "c1 zero";
}
if (std::get<C1>(arg).v > 0) {
return "c1 pos";
}
return "c1 neg";
}
if constexpr (std::is_same_v<T, B1>) {
return "cb1";
}
if constexpr (std::is_same_v<T, B1>) {
return "cb2";
}
if constexpr (std::is_same_v<T, C2>) {
return to_string(std::get<C2>(arg));
}
return "";
};
return std::visit<std::string_view>(visitor, variant);
}
} // namespace foo2
namespace foo3 {
auto to_string(const Foo& foo) {
switch (foo.index()) {
case 0:
if (std::get<0>(foo).v == 0) {
return "c1 zero";
}
if (std::get<0>(foo).v > 0) {
return "c1 pos";
}
return "c1 neg";
case 1:
switch (std::get<1>(foo).bar.index()) {
case 0:
return "cb1";
case 1:
return "cb2";
}
}
return "";
}
} // namespace foo3
namespace foo4 {
template<class... Ts> struct match : Ts... { using Ts::operator()...; };
template<class... Args>
std::string_view to_string(const std::variant<Args...>& foo) {
match data{
[](const C1& c1) {
if (c1.v == 0) {
return "c1 zero";
}
if (c1.v > 0) {
return "c1 pos";
}
return "c1 neg";
},
[](const C2& c2) { return to_string(c2.bar); },
[](const B1&) { return "cb1"; },
[](const B2&) { return "cb2"; }
};
return std::visit<std::string_view>(data, foo);
}
} // namespace foo4
int main() {
Foo f = C1{1};
std::cout << foo1::to_string(f) << std::endl;
std::cout << foo2::to_string(f) << std::endl;
std::cout << foo3::to_string(f) << std::endl;
std::cout << foo4::to_string(f) << std::endl;
}
Да миллион способов написать тоже самое на C++ вот первые пришедшие в голову, да оно чуть более многословное но зато гораздо эффективнее
"чуть более многословное", а под спойлером 124 строчки кода вместо 10 )))
Примеры неэквивалентны: если есть variant<A, variant<A, B>>, то 4 вариант, например, все А обработает одинаково, независимо от уровня вложенности.
Согласен можно конкретизировать и немного убрать лишнее парой строчек
namespace foo5 {
template<class... Ts> struct match : Ts... { using Ts::operator()...; };
template<class... Types, class... Fn>
constexpr auto mather(const std::variant<Types...>& foo, Fn... fn) {
return std::visit(match{std::forward<Fn>(fn)...}, foo);
}
template<class... Args>
constexpr std::string_view to_string(const std::variant<Args...>& foo) {
return mather(foo,
[](const C1& c1) {
if (c1.v == 0) {
return "c1 zero";
}
if (c1.v > 0) {
return "c1 pos";
}
return "c1 neg";
},
[](const C2& c2) {
return mather(c2.bar,
[](const B1&) { return "cb1"; },
[](const B2&) { return "cb2"; }
);
}
);
}
}
Ну можно тогда до конца пойти и убрать лямбду в лямбде:
template<typename... Ts>
struct overload : Ts... { using Ts::operator()...; };
template<typename T, typename... Fs>
class lvl
{
public:
lvl(Fs... fs) : ov{fs...} {}
template<typename U>
requires (std::is_same_v<std::decay_t<T>, std::decay_t<U>>)
decltype(auto) operator()(U u) {
return std::visit(ov, u);
}
private:
overload<Fs...> ov;
};
template<typename T, typename... Fs>
auto l(Fs... fs) { return lvl<T, Fs...>(fs...); }
struct A {};
struct B {};
struct C {};
using v1 = std::variant<A, B>;
using v2 = std::variant<A, int, v1>;
void test(v2 v)
{
auto matcher = overload{ [](int i) { /*...*/ },
[](A) { /*...*/ },
l<v1>( [](A) { /*...*/ },
[](B) { /*...*/ } ) };
std::visit(matcher, v);
}
(и лишнюю функцию он вырезал, в отличие от плюсов).
кто это сказал, что она лишняя? Добавите флаг, чтобы он мержил одинаковые функции - он будет мержить, иначе это некорректно например из-за адресов функций.
Если у вас появляются функции с одинаковым содержанием, то вы что-то делали не так
Не подскажете название? В таких деталях я не спец.
этим занимается линкер, ему флаг и передавайте, со стороны компилятора -ffunction-sections
Вполне нормальная ситуация при
неправильном коде
так может стоит просто добавить constexpr? https://gcc.godbolt.org/z/qfsqGvxeb
Помогает не генерировать одинаковый код для разных типов или я не понял в чем была претензия тогда.
Плюсы
direct
иpatterns
компилируют в один и тот же код, но и ghc компилируетdirect
иpatterns
не то что в один и тот же код, а вообще вырезаетdirect
и проксирует вызов вpatterns
:
а толку то когда реальный выхлоп всё ещё в 10раз больше
а все эти "кучу интересных вещей, вроде worker/wrapper " резко обламываются когда сталкиваются с реальным миром и реальным железом и уж тем более когда речь заходит о реальной оптимизации под железо. Очень было бы интересно посмотреть на пример где это "на практике" быстрее.
Оно гораздо более многословно, особенно когда у вас будет больше одного аргумента, или в некоторых guard'ах будут какие-то действия, и так далее.
Нет не будет просто не нужно писать на C++ как на хаскеле, стоит не забывать что эти примеры сделаны специально максимально приближенными к хаскель стилю что в реальности не имеет ни какого реального смысла, это просто пример гибкости языка и в проде я бы ни когда так писать не стал.
Более того, в C++-версии очень легко случайно сделать UB, которое компилятор вам нифига не поймает (привет Kelbon'у с его «ловит всё»), даже если его можно вырезать: https://gcc.godbolt.org/z/TzohY44bW . ghc пофиг на лишний вызов, он генерит код как раньше, а вот в C++ вся эта красота разваливается.
"Доктор, когда я делаю вот так, у меня тут болит"
Нормально там всё оказывается, чай, продакшен-левел компилятор, а не идрис какой-нибудь :]
питон тоже уже лет 30 как продакшен реди только быстрым его это не делает
Ну это очень удобно, конечно
конечно удобно ведь c++ не ограничен какой то одной парадигмой и при столкновении с реальным миром у которого есть и глобальные состояния и модифицирующие действия не нужно переходить на другой язык чтоб "не нарушать, "красивую" идею pure functional
Так что будь хаскель таким быстрым и удобным как вы его описываете геймдев был бы не на c++
и если честно я половину объяснения про wrapperы просто скипнул, по мне это все абсолютно бессмысленная и бесполезная дичь, в реальном мире у нас есть конкретнаая память, конкретные буфферы и данные в байтих. я даже примерно не представляю что в ghc это такое "боксингПослеG ∘ g' ∘ анбоксингДоG ∘" и в какой ассемблер это превращается и уж тем более как при всей этой мат.абстракции он может использовать векторные инструкции, с c++ для меня все плюс минус пркдсказуему.
ну там, SSA, раскраски графов всякие, всё такое
да там норм, там это относится к реальности и операциям в реальном железе, а как все эти композиции функций, манады и "бесконечные ленивые списки" к реальности относятся я не понимаю.
А в хаскеле нужно, что ли?
сразу извиняюсь за глупыый вопрос, а в хаскеле можно написать io монаду на чистом хаскеле?
Угу, я тут в соседнем треде пример приводил
странный кейс, я бы посмотрел, но я не про предсказуемость циферок, а про предсказуемость итоговой стейт машины в которую компилится мой код. когда я пишу что A вызывает в цикле B то я это и вижу в ассемблере, в заинлайненом виде или же с разворачиванием циклов не суть важно
Легаси-причины — имеющиеся
геймдев убъется за лишние пару процентов перфоманса так что не думаю что дело только в "легаси", так же как гугл, майки и все прочие мега корпорации тратящие огромные деньги на то чтоб ещё больше оптимизировать C++ каждый год чтоб экономить на своих серверах
А в C++ можно написать аллокатор памяти на чистом C++, без ассемблера и дёрганья железа?
а кто запрещает то? я то чтоб большой писатель кастомных аллокаторов, но tcmalloc как то же написан на C++, как то ядро linux на C написано, много там ассемблера то?
гугл придумал go
вот только почему то свой поисковой движёк на него не перевел
что значит не полезет в ядро? ОС занимается меджментом памяти, на микро контроллерах размечается память на старте, но вызов сискола это ещё не ассемблер да и причем тут сравнение с ассемблером, C и C++ можно напрямую делать вставку ассемблерного кода и в этом нет ни каких идеологических противоречий, не помню что б в хс можно было иметь глобальные объекты и уж тем более вставки кода на C делать, так что эта попытка притянуть то что в C есть ассемблер не в тему, они специально написаны так чтоб можно было использовать асм напрямую и от него не отгораживаются в отличие от всяких функциональных языков которые пропагандируют "чистые функции"
Правда? Научите делать сисколлы без ассемблера плиз.
не благодари: https://man7.org/linux/man-pages/man2/syscalls.2.html
семантику чего?
Можно, конечно.
Вот чего не знал того не знал, это забавно.
Не вижу ничего плохого в огораживании от потенциально опасных вещей при возможности их всё равно делать.
я вижу в этом небольшое лицемерие, как в расте с его unsafe и криками на каждом шагу "у нас самый безопасный язык"
Там пропагандируют не чистые функции, а контроль за эффектами в типах.
я возможно дурак, но не вижу разницы
Что, по-вашему, происходит, когда вы делаете
read(2)
вызывается другой сишный код) это не апи в том же понимании что и в языках типа хаскель, это уроверь ядра ОС, не помню что б хаскель на голом железе мог запускать в отличае от C, что б ввязываться вообще в эту дискуссию на тему "а чёй то у вас под капотом ассемблер есть" он и у хаскеля есть, только страшный и не красивый)
Ну деюро нет, дефакто есть, так что какая разница?) и опять же за них ни кто в C/C++ мире не осудит, в отличае от функциональщиков.
В чём лицемерие?
Ну в том что безопасность всей системы определяется её слабейщей частью, все же ругают С++ за "небезопасную" работу с памятью, а то что там есть миллиарды способов как её делать безопасно и сколько есть тулинга чтоб находить эти проблемы в сравнение с другими языками в расчёт почему то не берется. В этом и лицемерие.
А ещё в том что "мы против глобальных объектов, мутабельности и сделаем все чтоб это было делать больно и сложно", но оказывается что без этого язык совершенно бесполезен и все же окошечко в реальный мир пробивать приходится и не суть важно как, будь то монада IO или unsafe. И вот в этот момент я всегда задаюсь вопросом, а зачем мне трудности самому себе создавать, когда можно просто использовать правильный тулинг и style гайды, в место того чтоб маяться дурью в виде борьбы с borrow чекером. Замени все сырые указатели на shared/weak поинтеры и 90% проблем будут так же просто решены. Если хочется ещё ближе к расту можно заменить на uniq и вообще отличий практически не будет.
Любопытно. Но если вы сами прорубите окно в реальный мир, то окажется что ваши зависимости не следуют вашим style гайдам. Более того, они и тулингом могут быть не покрыты, и тестами.
А вот компилятор может обнаружить ошибку сразу, без доп инструментов, и его невозможно не запустить.
Кстати, тулинг ловит не все ошибки, об этом чуть выше писали.
А С++ ругают за тысячи UB, из которых многие можно было бы сделать хотя бы unspecified behavior или platform-dependent.
А что мешает зависимостям раст нагадить утечками в unsafe? в C++ у меня хотя бы есть valgrind, статические анализаторы кода, адрессанитайзеры и ещё миллиард всего что поможет мне найти эту проблему и решить.
1) Тем что стандартная библиотека Rust почти на 100% формально верифицирована именно чтобы устранить баги с unsafe.
https://github.com/rust-lang/miri-test-libstd
2) Тем что в Rust принято формально верифицировать библиотеки, работающие с unsafe. Для этого есть соответствующие инструменты (https://github.com/rust-lang/miri/, https://github.com/formal-land/coq-of-rust и т.д.), и ими реально пользуются.
3) Наконец есть valgrind, адрессанитайзеры и прочие инструменты, которые работают как с С++, так и с Rust.
Статические анализаторы тоже есть, хотя не совсем понятно зачем они нужны после всего вышеперечисленного.
miri это никакая не "формальная верификация" это санитайзер
coq уже больше похоже на правду, но реально им никто не пользуется
наличие санитайзеров для раст подтверждает что нифига формально не верифицировано и по факту для отлова уб используются утилиты заимствованные из С++. Только интеграция с раст там хуже.
Ок, по п.1 мне нужно было скинуть ссылку из п.2. И тем не менее:
libcore https://github.com/formal-land/coq-of-rust/tree/main/CoqOfRust/core
liballoc https://github.com/formal-land/coq-of-rust/tree/main/CoqOfRust/allocсм. п1
Каким образом Miri заимствован из C++? Кстати модель ссылок StackedBorrows в нем формально верифицирована. И некоторые другие его части тоже.
Ок, по п.1 мне нужно было скинуть ссылку из п.2.
Эти два пункта вообще не связаны, кок и мири это два разных подхода.
И тем не менее:
и тем не менее в тех директориях гита просто кодоген который гоняет код из одной репрезентации в другую. Каким образом он что то верифицирует?
Каким образом Miri заимствован из C++
зачем вы пытаетесь передергивать? И ежу очевидно что мой комментарий был про все остальные сантайзеры из вашего пункта (3). По miri это был пункт (1). Но рад что вы согласились что мири это санитайзер а не формальная верификация.
Кстати модель ссылок StackedBorrows в нем формально верифицирована.
Речь о проверке кода, при чём тут верификация какой-то модели? Вы сели в лужу со своим предыдущим тезисом и теперь пытаетесь приплести слово верификация хоть куда-то.
по факту для отлова уб используются утилиты заимствованные из С++
Нет, это вы сели в лужу, т.к. по факту везде используется miri, не имеющий отношения к С++. Но если очень хочется, то можно использовать valgrind и т.д.
coq уже больше похоже на правду, но реально им никто не пользуется
Я отвечал на это, и привел пример что используется.
по факту везде используется miri, не имеющий отношения к С++.
Наконец есть valgrind, адрессанитайзеры и прочие инструменты, которые работают как с С++, так и с Rust.
Вы там определитесь какие инструменты используются а какие нет, и тогда пишите. Подсказка: используются разные т.к. мири проверят только малую часть проблем, а именно правила бч, и то теряет информацию когда происходит много кастов в сырые поинтеры.
Я отвечал на это, и привел пример что используется.
И что же там используется? Есть какой-то транслятор, а сами пруфы не наблюдаются. И это только стандартная либа, а еще для сотен крейтов никто даже этот трансформатор вообще не запускал. И вы не поверите, но для си тоже есть методы формальной верфикацией.
Нет, пример был про Miri, в нем формально проверили правила borrow checker, заодно проверив и компилятор в части borrow checker. Вы же утверждали, что coq не используется совсем.
Кроме того проверять нужно только крейты, который активно используют unsafe, но при этом нет unsafe во внешнем API. В моей предметной области (микроконтроллеры) таких ровно 2: embassy-sync и heapless.
Кроме того проверять нужно только крейты, который активно используют unsafe, но при этом нет unsafe во внешнем API.
таких 20 процентов согласно раст статистике
Тем что стандартная библиотека Rust почти на 100% формально верифицирована именно чтобы устранить баги с unsafe. <Ссылка miri-test-libstd>
Вот тут вы рассказли про верефикацию стд библиотеки и привели ссылку на мири. Мири не делает формальную верификацию стд библиотеки потому что мири это санитайзер. Вы ошиблись. Я указал вам на эту ошибку.
Нет, пример был про Miri, в нем формально проверили правила borrow checker
Фраза "формально проверили правила borrow checker" сама по себе не имеет смысла. Что именно проверили? Что правила не противоречивы между собой? Выражайтесь в следущий раз яснее. Возможно для этого полезно ознакомится с темой более глубоко прежде чем делать громкие заявления.
Вы же утверждали, что coq не используется совсем.
Где я это утверждал? Что значит совсем? Вообще в мире?
таких 20 процентов согласно раст статистике
Статистику в студию!
Вот тут вы рассказли про верефикацию стд библиотеки и привели ссылку на мири. Мири не делает формальную верификацию стд библиотеки потому что мири это санитайзер. Вы ошиблись. Я указал вам на эту ошибку.
Согласен, тут я ошибся.
Где я это утверждал? Что значит совсем? Вообще в мире?
Вы утверждали, что coq никто не пользуется в Rust. Не передергивайте, вот ваша цитата:
coq уже больше похоже на правду, но реально им никто не пользуется
Далее:
Фраза "формально проверили правила borrow checker" сама по себе не имеет смысла. Что именно проверили? Что правила не противоречивы между собой?
Выражайтесь в следущий раз яснее. Возможно для этого полезно ознакомится с темой более глубоко прежде чем делать громкие заявления.
Предлагаю вам ознакомиться с первоисточником:
In this work, we propose Stacked Borrows, an operational semantics for memory accesses in Rust. Stacked Borrows defines an aliasing discipline and declares programs violating it to have undefined behavior, meaning the compiler does not have to consider such programs when performing optimizations. We give formal proofs (mechanized in Coq) showing that this rules out enough programs to enable optimizations that reorder memory accesses around unknown code and function calls, based solely on intraprocedural reasoning. We
also implemented this operational model in an interpreter for Rust and ran large parts of the Rust standard library test suite in the interpreter to validate that the model permits enough real-world unsafe Rust code
Статистику в студию!
тут https://foundation.rust-lang.org/
coq уже больше похоже на правду, но реально им никто не пользуется
Вы утверждали, что coq никто не пользуется в Rust
Так а сколько кода реально верифицировано? Думаю мало. В коммерческой разработке я думаю это не выгодно. А так да, можно вспомнить https://frama-c.com/ , тоже верификация. Тут утилита семантику си понимает. В случае с растом была история c проверкой std::
, но афаик было много ручной работы всё равно. И с тех пор уже сколько изменений внесли в код, а статью выпустили один раз, грант отработали и всё. Что-то автоматическое есть по вашей ссылке, но там собственно самих пруфов не видно. Так что пока это всё реклама.
Предлагаю вам ознакомиться с первоисточником
С таким успехом я могу первоисточник сам прочесть. Зачем на хабр заходить...
Ага, и там же написано:
Most of these Unsafe Rust uses are calls into existing third-party non-Rust language code or libraries, such as C or C++.
Так что все зависит от ваших зависимостей. Если добавлять библиотеки-обертки из C, C++ и других языков, то там конечно будет unsafe. Ну и конечно API ОС:
In fact, the crate with the most uses of the
unsafe
keyword is the Windows crate [7], which allows Rust developers to call into various Windows APIs.
Впрочем то же самое будет в любом языке, использующем код другого языка, только не будет помечено unsafe (хотя в C-Sharp есть свой unsafe).
Но соотношение 20/80% намекает, что для часто используемых областей написаны библиотеки на чистом Rust без единой строчки unsafe.
Так а сколько кода реально верифицировано? Думаю мало.
Я к тому и веду, что вы тоже были не правы, что coq совсем не используется. А сейчас выяснилось, что вы читали первоисточник, и все-же категорично утверждали обратное.
Так замеры делало rust foundation. Всё что написано в этой статье нельзя считать объективной информацией. Понятно что задача раст евангилистов убедить нас в том что это нестрашно, рассказать что это виновата ОС и кто угодно кроме них. А чтобы оценить сколько реально ансейф процентов в итоговом продукте, надо сравнивать сколько функционала за теми С/С++ обертками. При том как то умнее чем просто по количеству строк, потому что раст очень вербозный.
к тому и веду, что вы тоже были не правы, coq совсем не используется. А сейчас выяснилось, что вы читали первоисточник, и все-же категорично утверждали обратное.
Утверждал что не используют для верифиакции библиотек. То что модель мири как-то покрутили в коке на предмет совместимости с какой-то моделью оптимизаций - не знал.
Так замеры делало rust foundation. Всё что написано в этой статье нельзя считать объективной информацией. Понятно что задача раст евангилистов убедить нас в том что это нестрашно, рассказать что это виновата ОС и кто угодно кроме них. А чтобы оценить сколько реально ансейф процентов в итоговом продукте, надо сравнивать сколько функционала за теми С/С++ обертками. При том как то умнее чем просто по количеству строк, потому что раст очень вербозный.
Они просто подсчитали крейты, в которых есть хотя бы 1 строчка unsafe. Это вполне объективная информация для данной простой модели подсчета.
Of those 127,000 crates, 24,362 make use of the unsafe keyword, which is 19.11% of all crates. And 34.35% make a direct function call into another crate that uses the
unsafe
keyword. [6] Nearly 20% of all crates have at least one instance of theunsafe
keyword, a non-trivial number.
Другую статистику никто не посчитал. Но не потому что это заговор каких-то там евангелистов, а потому что это сложнее, и надо договориться по методике которая всех устроит.
Но если подсчитать, то может внезапно выясниться, что среди тех 19.11% крейтов многие написаны во времена выхода Rust 1.0 и не поддерживаются (аналогичные проблемы есть и в других пакетных менеджерах, например npm).
Кроме того надо еще исключить пакеты с 10 строчками кода, выложенные ну просто потому что хотелось попробовать выложить (опять те же претензии к npm).
Вобщем все сложно, но ведь это вы сослались на эту статистику, и постом ниже уже считаете её необъективной xD.
Понятно что задача раст евангилистов убедить нас в том что это нестрашно, рассказать что это виновата ОС и кто угодно кроме них.
Не возможно и не нужно переписывать все библиотеки мира на Rust. Нужно соблюдать разумный баланс.
То же относится к любым языкам, которые используют библиотеки других языков.
На Python вообще половина PyPi это обертки над С, но никто не жалуется. При этом код по сути unsafe, но ключевого слова такого нет, и за счет этого выглядит приятнее)
Так что вы предлагаете то? Или приведите пример другого языка, где FFI вделан лучше и безопасней.
Утверждал что не используют для верифиакции библиотек. То что модель мири как-то покрутили в коке на предмет совместимости с какой-то моделью оптимизаций - не знал.
Ок, принято.
Как раз то что ансейф есть в 1/5 крейтов это ок. Я вам это и сказал в первом сообщении. А все остальные цитаты и выводы из поста что вы привели, - это уже потенциально реклама.
Но если подсчитать, то может внезапно выясниться, что среди тех 19.11% крейтов многие написаны во времена выхода Rust 1.0 и не поддерживаются (аналогичные проблемы есть и в других пакетных менеджерах, например npm).
то же самое может внезапно оказаться для оставшихся 80%
Кроме того надо еще исключить пакеты с 10 строчками кода, выложенные ну просто потому что хотелось попробовать выложить (опять те же претензии к npm).
их же надо исключить из 80% пакетов без ансейф. И я уверен что те кто хотели просто "попробовать выложить" или выложить свое домашнее задание из универа выкладывали как раз крейты без ансейф. В итоге доля реальных библиотек с ансейф увеличивается.
Не возможно и не нужно переписывать все библиотеки мира на Rust.
Скажите это растоманам которые бегают по гитхабу и создают ишью вида "consider rewriting in rust". Недавно один уникум даже в Qt багтрекере такое создал. Но наверное на созданиях тикетов деятельность по переписыванию на раст обычно и заканчивается =)
На Python вообще половина PyPi это обертки над С, но никто не жалуется.
Питон не позиционирует себя как замену Си. Питон наоборот позиционирует себя как язык клей с реплом и прочим. И используется например как конфигуратор C++ ML библиотек.
Напомню ветка началась с обсуждения зависимостей. Вы жаловаолись что зависимости на си или си++ это плохо, они могут быть не протестированы санитайзерами, не следовать современным практикам и т п... А теперь оказывается "это обертки над С, но никто не жалуется" :-D
Как раз то что ансейф есть в 1/5 крейтов это ок. Я вам это и сказал в первом сообщении. А все остальные цитаты и выводы из поста что вы привели, - это уже потенциально реклама.
Значит я вас неправильно понял. Думал вы в негативном смысле написали.
то же самое может внезапно оказаться для оставшихся 80%
Но в те времена стандартная библиотека была гораздо хуже, и некоторые сейчас очевидные вещи нельзя было сделать без unsafe. По-этому распределение может быть в пользу пакетов с unsafe, но конечно надо проверять.
Скажите это растоманам которые бегают по гитхабу и создают ишью вида "consider rewriting in rust". Недавно один уникум даже в Qt багтрекере такое создал. Но наверное на созданиях тикетов деятельность по переписыванию на раст обычно и заканчивается =)
Я на Rust пишу с 2018 примерно, но по гитхабу так не бегаю) И я ни разу не видел чтобы переписали какой-то проект целиком на Rust (не исключаю что такое существует).
Но видел много новых библиотек, аналогичных по функционалу уже существующим на С и С++, но с другим API. Например eigen в С++ и nalgebra в Rust. Хотя казалось бы математика везде одинакова.
Напомню ветка началась с обсуждения зависимостей. Вы жаловаолись что зависимости на си или си++ это плохо, они могут быть не протестированы санитайзерами, не следовать современным практикам и т п... А теперь оказывается "это обертки над С, но никто не жалуется" :-D
Я сейчас отлистал вверх и не нашел начало ветки от Arenos, похоже он удалил сообщение (пора уже делать скриншоты..).
Но он заявлял что санитайзеры и современные практики для него лучше подходят, а компилятор Rust слишком сильно ограничивает. На это я ему и ответил, что
могут быть не протестированы санитайзерами, не следовать современным практикам и т п..
Так что я от своих слов не отказываюсь, а вы их вырвали из контекста.
Я считаю что больше гарантий от компилятора это лучше. Но Haskel пока не освоил.
Например eigen в С++ и nalgebra в Rust
До const-generics сделать что-то отдаленно похожее на eigen в расте было нельзя совсем. Сейчас const-generic тоже могут мало, для поднятия на уровень типов приходится городить такие приколы
https://docs.rs/nalgebra/0.32.1/nalgebra/base/dimension/trait.ToTypenum.html
https://docs.rs/nalgebra/0.32.1/nalgebra/base/dimension/trait.ToConst.html
с конвертацией через S-List. Кстати очень приятно потом лицезреть эти типы в километровых ошибках компиляции.
Это даже по сравнению с С++ нулевых годов смешно.
А ключевая фишка eigen в составлении пайплайнов вычисления, а потом его обработке. Эту тему я уже затрагивал тут в комментах на примере ренжей.
https://devdocs.io/eigen3/topicinsideeigenexample
В расте такого нету и сделать невозможно. Там сложение создаёт новую матрицу, а для всех симд операций отдельно захардкодены явные методы (если вообще захардкодены). Похожая идея есть и в numpy, больше в pytorch, но в питоне это всё в рантайме. С++ даёт это выразить в компайл тайм.
В nalgebra даже transpose()
копирует матрицу, а transpose().transpose() делает две копии. В то время как в нормальном фреймворке эти операции не делают ничего вообще.
Вот это уже предметное обсуждение.
До const-generics сделать что-то отдаленно похожее на eigen в расте было нельзя совсем. Сейчас const-generic тоже могут мало, для поднятия на уровень типов приходится городить такие приколы
Да, const-generics пока все не очень. Надеюсь допилят.
Там сложение создаёт новую матрицу, а для всех симд операций отдельно захардкодены явные методы (если вообще захардкодены).
В eigen автоматически выбирается вариант симд, а в nalgebra нужно явно передать с использованием внешних ящиков. Не могу сказать что способ eigen всегда лучше (хотя он всегда проще), зависит от алгоритма. В некоторых случаях нужно подбирать размер симд операции (например AVX 4,8 или 16 слов) чтобы добиться максимальной производительности.
https://www.rustsim.org/blog/2020/03/23/simd-aosoa-in-nalgebra/
Про новую матрицу не понял, поясните.
В nalgebra даже
transpose()
копирует матрицу, а
transpose().transpose() делает две копии. В то время как в нормальном фреймворке эти операции не делают ничего вообще.
Ну это проблема в библиотеке, хз почему не реализовали.
В любом случае, nalgebra это был просто пример того, что библиотеки обычно не переписывают с С и С++ на Rust, а создают новые.
Я же объяснил, потому что eigen не выразим на расте.
Пока не выразим. Но напомните мне, через сколько лет в С++ добавили шаблоны?
И вообще это неконструктивный подход. Я тоже могу сказать, что embassy не выразим в С++. Просто потому что системы типов разные.
В чем неконструктивность если я просто констатирую факты?
А так да, будем ждать ¯_(ツ)_/¯
Я вам развернуто написал что на мой взгляд в eigen хорошо сделано, а что плохо. И чего не хватает в nalgebra и Rust.
Вы же просто заявляете "eigen не выразим на расте", и отказываетесь от нормального обсуждения. Вот это неконструктивно.
По этому возвращаю вам обратно:
Я тоже могу сказать, что embassy не выразим в С++.
Мне лень участвовать в демагогии что конструктивно а что нет.
Фичей eigen в nalgebra нету потому что их невозможно реализовать на расте.
А я вам написал, что эти фичи eigen делают невозможным выбор оптимального варианта SIMD. И это в С++, где производительность - объект поклонения каждого программиста.
А вот в Rust выбрать можно, ценой некоторого увеличения кода. При этом нельзя (пока) сделать автоматический выбор, как в eigen.
Но вы упорно отказываетесь обсуждать суть проблемы. Кто же из нас демагог?
нет, я ни чего не удалял, если только кто то этого не сделал за меня
и да лучше, компиль раста слишком много требует от разработчика чтоб быть хотя бы немного полезнее C++. И все эти проблемы которые он якобы решает через чур преувеличины и раздуты сообществом растоманов.
Но вы так и не ответили на вопрос: как вы в С++ контролируете что зависимости пользуются стайл-гайдами, тулингом и т.д?
И все эти проблемы которые он якобы решает через чур преувеличины и раздуты сообществом растоманов.
Проблемы кем только ни раздуваются: Microsoft, Android, АНБ, Linux и даже Unreal Engine. И они рекомендуют переходить на более безопасные языки в целом, а не только на Rust .
1) не брать либы в которых этого нет, 2) ни как, а зачем? Есть свои unit тесты, интеграционное тестирование, санитайзеры, статический анализаторы, нормальные отладчики, профайлеры и огромное комьюнити. За десяток лет я всего пару раз находил баги в 3rd-party и правил их и то только потому что брал самые последние версии ещё не обкатанные комьюнити. И ни каких проблем с этим не увидел. Я так понимаю ошибок в 3rd-party на других языках ни когда не бывает? или к чему это?
Ну как только этот безопасный язык напишут и переведут все библиотеки на него так сразу и перейдем на этот волшебный безопасный и быстрый язык.. Но пока что все эти же компании спонсируют cppcon и активно участвуют в собраниях комитета по стандартизации и развитию c++ и пропихивают свои пропозалы, а некоторые ещё и компиляторы зачем то свои пишут..
И в зависимостях зависимостей проверяете?) Кроме того наличие тулинга не говорит о степени покрытия.
А баги вышеупомянутые крупные компании находят регулярно. Значит либо у вас уникальный набор зависимостей без багов, либо вы эти баги не замечаете.
Я так понимаю ошибок в 3rd-party на других языках ни когда не бывает?
Очевидно что в других языках часть багов не возможна, другая часть приводит к обычному падению с ошибкой и стектрейсом.
А доля гейзенбагов, невоспроизводимых в тестовом окружении гораздо меньше.
Но пока что все эти же компании спонсируют cppcon и активно участвуют в собраниях комитета по стандартизации и развитию c++ и пропихивают свои пропозалы, а некоторые ещё и компиляторы зачем то свои пишут..
А так же спонсируют другие языки, конференции и т.д. Так что это не аргумент.
Очевидно что в других языках часть багов не возможна
это какие и в каких что их прям "не может быть"? утечка памяти и ресурсов в целом в языка с GC вроде как не уникальное явление, так же как и выход за границы, обращение к неинициализированным данным, дедлоки и всё остальное.
к обычному падению с ошибкой и стектрейсом.
а в C++ какое то необычное падение и стектрейсы? или оно в случае ошибки берет и сносит ОС? если мы конечно сравниваем языки в одинаковых задачах
не аргумент.
конечно аргумент ведь зачем они тратят усилия на всё это если cчитают C++ таким не исправимым, ну или там есть некий контекст все же, а не просто заявления в стиле "бегите глупцы" какими их обычно пытаются представить раст фанатики.
И в зависимостях зависимостей проверяете?)
Ты либо весь код собираешь с санитайзерами либо ни какой, мне трудно представить как собрать половину так половину сяк.
это какие и в каких что их прям "не может быть"? утечка памяти и ресурсов в целом в языка с GC вроде как не уникальное явление, так же как и выход за границы, обращение к неинициализированным данным, дедлоки и всё остальное.
Например use after free. Пропробуйте вызвать такую ошибку в C-Sharp или Java. Да и в Go врядли удастся.
Ну а уж UB при переполнении signed int я больше нигде не видел.
а в C++ какое то необычное падение и стектрейсы? или оно в случае ошибки берет и сносит ОС? если мы конечно сравниваем языки в одинаковых задачах
В С++ выход за границы массива в лучшем случае вызовет segfault, который расследовать несопоставимо сложнее, чем нормальную текстовую ошибку + стектрейс.
В худшем случае это запортит очень важные данные, которые запишутся в БД, или приведут к уязвимости и взлому. Или к гейзенбагу.
конечно аргумент ведь зачем они тратят усилия на всё это если cчитают C++ таким не исправимым, ну или там есть некий контекст все же, а не просто заявления в стиле "бегите глупцы"
Они считают что нужно поддерживать легаси код, но не рекомендуют писать новый на С++.
Вы можете с этим не соглашаться, но странно делать вид, что этих заявлений не было. Вот например от АНБ
https://media.defense.gov/2022/Nov/10/2003112742/-1/-1/0/CSI_SOFTWARE_MEMORY_SAFETY.PDF
ну предположим нельзя, хотя мне кажется можно найти нетривиальные кейсы когда CSharp взаимодействует с какими ни будь ресурсами типа COM, но ценой чего? добавления GC? потерями перфоманса или часовыми плясками во круг borrow checker чтоб он соизволил понять что тут всё нормально?
а что собственно страшного в переполнении signed int и в чем принципиальное отличие от того что в других языках оно defined behavior хотя и делает тоже самое? я видимо просто не работал там где это критичная проблема хотелось бы понять проблематику, а то столько раз уже про это слышал но не разу не видел
вот уж чьё мнение меня меньше всего интересует в вопросах "безопасности кода" так это агентства по слежке и созданию 0 day уязвимостей везде где сможет дотянутся.
В худшем случае это запортит очень важные данные,
в других языках же такого не бывает совсем, привет от Log4j. Но почему то все критически важные узлы чувствительные ко взлому и отказоустойчивости всё равно пишутся на C и С++
ну предположим нельзя но, хотя мне кажется можно найти нетривиальные кейсы когда CSharp взаимодействует с какими ни будь ресурсами типа COM, но ценой чего? добавления GC? потерями перфоманса или часовыми плясками во круг borrow checker чтоб он соизволил понять что тут всё нормально?
Вы уж определитесь, GC или borrow checker.
Современные GC собирают очень шустро и могут не останавливать потоки для своей работы. Для абсолютного большинства задач это подходит.
Если вам не нравится GC и Rust, выбор довольно мал. Возьмите Zig.
а что собственно страшного в переполнении signed int и в чем принципиальное отличие от того что в других языках оно defined behavior хотя и делает тоже самое?
Конкретно в переполнении ничего страшного. Страшно что это UB, а любое UB может сломать программу максимально странным способом: например конечный цикл может стать бесконечным!
Тут есть и другие примеры:
https://github.com/Nekrolm/ubbook/blob/master/numeric/overflow.md
вот уж чьё мнение меня меньше всего интересует в вопросах "безопасности кода" так это агентства по слежке и созданию 0 day уязвимостей везде где сможет дотянутся.
Мнение Microsoft и Android (Google) для вас тоже ничего не значит? А как насчет Линуса Торвальдса?
в других языках же такого не бывает совсем, привет от Log4j. Но почему то все критически важные узлы чувствительные ко взлому и отказоустойчивости всё равно пишутся на C и С++
Они пишутся по:
1) Историческим причинам
2) Требованиям к FFI: библиотеку должно быть легко подключить к любому языку, так что С, но не С++
3) Требованиям к памяти: нужно гарантировать, что память в памяти не останется важной информации после освобождения объекта
Но процесс идет, и например Android переписывает кучу своего кода с С и С++ на Kotlin и Java, а небольшие критичные куски на Rust.
например конечный цикл может стать бесконечным!
Прикольно. А почему так происходит?
Типа, оптимизатор думает, что, раз мы зашли в отрицательную ветку для печати отрицательного числа, то можно уже на сравнение с девяткой не заходить?
Вы уж определитесь, GC или borrow checker.
Аксиома Эскобара
Возьмите Zig.
мм, язык с 40 летним опытом, кучей тулинга, комунити, десятками ide или же язык не выбравшийся из пеленок, хмм, ну выбор очевидный конечно)..
Страшно что это UB,
меня мало интересуют синтетические примеры, можно пример из жизни вот у нас оказалось переполнение и все взорвалось? А бесконечные циклы и без переполнения знаковых могут случится и не только в С++, детектится это на раз два по дампу или профайлеру, да и отлавливается ещё на этапе статических анализаторов обычно.
Мнение Microsoft и Android (Google) для вас тоже ничего не значит? А как насчет Линуса Торвальдса?
Линуса значит, вот только то что он согласился на поддержку раста как языка для драйверов ни какого отношения к "не безопасности" C++ не имеет, и его претензии к плюсам в целом мне понятны и в большинстве своём обоснованы , но они опять же ни как не связаны с "не бесопасным кодом"
так что С, но не С++
ты же знаешь что glibc в андроиде на C++ написан? и то что не проблема писать на С++ и иметь при этом внешний апи на C?
Но процесс идет
ну это только время покажет, эти мантры уже были за последние 30 лет и не раз так что очень слабо верится что что то изменится
Вы спрашивали:
а что собственно страшного в переполнении signed int и в чем принципиальное отличие от того что в других языках оно defined behavior хотя и делает тоже самое?
А теперь заявляете:
меня мало интересуют синтетические примеры
Похоже вы не читали другие примеры по ссылке. Вычисление хэша строки тоже синтетический пример?
Ну а в реальной жизни полно уязвимостей, вызванных UB переполнения int, раздел Observed Examples
https://cwe.mitre.org/data/definitions/190.html
А бесконечные циклы и без переполнения знаковых могут случится и не только в С++
В других языках эти ошибки будет легко воспроизвести и поправить код. А в C и С++ UB приведет к гейзенбагу, который будет наблюдаться раз в год при определенном положении Марса в созведии Козерога.
Линуса значит, вот только то что он согласился на поддержку раста как языка для драйверов ни какого отношения к "не безопасности" C++ не имеет, и его претензии к плюсам в целом мне понятны и в большинстве своём обоснованы , но они опять же ни как не связаны с "не бесопасным кодом"
Речь вообще не про Rust. Его претензии к С++ и С привели к появлению опций в gcc, чтобы можно было отключить некоторые UB ключем компиляции.
Ну а про Microsoft и Google вы совершенно случайно забыли ответить.
ты же знаешь что glibc в андроиде на C++ написан? и то что не проблема писать на С++ и иметь при этом внешний апи на C?
Если вот эта, то там от С++ одно название.
https://ru.wikipedia.org/wiki/Bionic_(библиотека)
ну это только время покажет, эти мантры уже были за последние 30 лет и не раз так что очень слабо верится что что то изменится
Очнитесь и выгляньте наконец в реальный мир. Он совсем не такой как в 2000 году:
Cloudflare целиком на Rust переписались, в исходниках Android его уже столько же, сколько C, AWS несколько сервисов целиком переписали и не останавливаются (и сделали Firecracker целиком на нем, опенсорс менеджер микровиртуалок, на котором работают Lambda и Fargate), с голенга многие тоже переписываются. Я могу дать полный список известных мне проектов, там от MESA до Figma, Vercel и части бэка npm.js репозитория
А бесконечные циклы и без переполнения знаковых могут случится и не только в С++
Например, в Фортране не может случиться неумышленный бесконечный цикл (хотя там есть специальный оператор бесконечного цикла). Там дело начинается с того, что в преамбуле вычисляется количество повторений цикла, и ровно столько оборотов он и делает, как бы ни изменялась управляющая переменная.
Ты либо весь код собираешь с санитайзерами либо ни какой, мне трудно представить как собрать половину так половину сяк.
А мне легко. Просто зависимость лежит в dll, которая предоставляется ОС, или хуже того вендором. И попробуйте туда внедрить санитайзер.
И мы возвращаемся к тому, что вам надо пройти по всему дереву зависимостей и все их перекомпилировать с санитайзерами и другими защитами. И надеяться что ничего не сломается.
В моем текущем проекте например 11 прямых зависимостей и 312 всего, нереально.
и как тут поможет любой другой язык в такой ситуации и в чем тут вина с++?
если вам нужно делать что то большее чем добавить пару флагов для сборки в каком ни будь текстовом файлике то у вас большие проблемы с CI.
Это мы уже обсудили:
Очевидно что в других языках часть багов не возможна, другая часть приводит к обычному падению с ошибкой и стектрейсом. А доля гейзенбагов, невоспроизводимых в тестовом окружении гораздо меньше.
И это будет работать для всего дерева зависимостей, кроме тех которые обертки над С библиотеками.
При этом в большинстве проектов вообще не будет зависимостей от С библиотек.
Просто зависимость лежит в dll, которая предоставляется ОС, или хуже того вендором.
А как в ней "грепнуть по unsafe" ?
Если вы перечитаете ветку, то мы обсуждали в основном языки с GC. Кроме того речь шла про санитайзеры, причем тут вообще unsafe?
Конкретно в Rust компиляция всего дерева зависимостей идет из исходников со статической линковкой. И cargo build вам выкачает всё дерево зависимостей, грепайте.
Если же dll это обертка над С или С++, или от вендора, то в ней по определению нечего "грепнуть по unsafe", ведь в С++ никакого unsafe нет.
Лучше вы сами внимательно перечитайте. В ветке было про сборку раст и с++.
В чем проблема в С++ делать компиляцию всего дерева из исходников со статической линковкой и нужными флагами санитайзера?
И они рекомендуют переходить на более безопасные языки в целом, а не только на Rust .
Я так понимаю ошибок в 3rd-party на других языках ни когда не бывает? или к чему это?
утечка памяти и ресурсов в целом в языка с GC вроде как не уникальное явление
Перечитал. Не согласен.
В чем проблема в С++ делать компиляцию всего дерева из исходников со статической линковкой и нужными флагами санитайзера?
Ага, бороться с зоопарком систем сборки, когда половина зависимостей настроена на динамическую линковку. Реально, но очень долго.
И все же что вы будете делать с dll от вендора?
Перечитал. Не согласен.
Читайте еще раз. Вы привели одну фразу про ГЦ, и пол фразы про другие языки. Одна цитата как раз про контроль качества зависимостей.
Даже если бы в ветке было бы в основном про гц, в чем проблема ответить на ту часть сообщение которую я считаю нужным прокоментировать?
Ага, бороться с зоопарком систем сборки, когда половина зависимостей настроена на динамическую линковку. Реально, но очень долго.
Не знаю о чём вы, у меня такого нету.
И все же что вы будете делать с dll от вендора?
Давайте начнём с того как вендор создаст dll на раст и что вы будете делать с ней?
Пробелмы поставки библиотек в бинарном формате это отдельная проблема. То что в расте это сделать нельзя говорит лишь об ограниченности раста и что он неприменим для вендоров блобов. Хотя скорее всего можно за сишным интерфейсом спрятать реализацию на расте, тогда возвращаемся к той же проблеме.
И прочитал еще раз. И все-равно мы там обсуждали "другие языки" и GC последние 6 сообщений как минимум.
Пробелмы поставки библиотек в бинарном формате это отдельная проблема. То что в расте это сделать нельзя говорит лишь об ограниченности раста и что он неприменим для вендоров блобов. Хотя скорее всего можно за сишным интерфейсом спрятать реализацию на расте, тогда возвращаемся к той же проблеме.
Разумеется можно:
crate-type = ["dylib", "rlib"]
Будет .so библиотека с кодом Rust без всякого C-API. И для вендоров всё хорошо.
Давайте начнём с того как вендор создаст dll на раст и что вы будете делать с ней?
В dll от вендора на практически любом языке не получится добавить санитайзеры, исходного когда тоже нет.
Так что придется поверить вендору что с ней всё хорошо. И полагаться на гарантии компилятора.
Например для CSharp я уверен что dll не портит память и не вызовет UB. Для Rust аналогично, если вендор мне ответит что там нет unsafe (разумеется если он не врет).
Компилятор C++ (и даже C) с хоть на что-то годным оптимизатором требует от разработчика куда большего: понимать, что может привести к валидно компилирующемуся но не ведущему себя некорректно коду, а тоже держать в голове то, что не допускает такого поведения в его коде.
Греп ничего не даёт. Инвариант необходимый для ансейф блока зачастую приходит снаружи
Если в ансейф блоках нету сложных зависимостей, то это скорее всего тривиальные обёртки на си библиотекой. Проблемы у вас будут со сложными случаями, а там синтаксический мусор вроде unsafe_unchecked и ужасная эрогономика сырых указателей только мешают. Недавно слышал историю что пришлось ансейф код с раста переписать на си, чтобы его можно было удобно прочесть и отладить. Потом исправленную реализацию запихнуили обратно в раст.
В стд есть проблемы с тем что им приходится писать везде __
чтобы не было конфликта с пользовательскими макросами. Это сигнал о том что разработчики стандартной библиотеки просто себя не уважают. А нам этот мусор ещё и читать приходится. Проблему с макросами можно было бы уже давно решить.
А концепты invocable или weakly_equality_comparable_with нёсут семантику, которая важна для логики программы. В то время как unsafe
, unchecked
, as mut T*
с точки зрения логики програмы ничего не меняют. Просто вставляют азерты.
Офигительно. А можно подробности?
какие?
Все утечки, грепая по unsafe не найти. В расте утечка - это safe, и никто вам не запрещает Box::leak или что-нибудь посложнее в safe-подмножестве языка сделать без единой строчки unsafe у себя.
Есть большая разница: утечка из-за ошибки в коде (в unsafe) и сознательно вызванная утечка через Box::leak. Почитайте документацию:
This function is mainly useful for data that lives for the remainder of the program’s life.
Box::leak не приведет к use after free, double free и т.д. Потому что гарантии borrow checker никуда не деваются, и после выхода этой ссылки из области видимости вы уже не сможете к ней обратиться.
Ни разу не писал на хаскеле, но иногда писал на плюсах. Но, в итоге, я более менее могу осознать пример кода выше на хаскеле, но совершенно не вкуриваю ваш код на плюсах
RTTI реализован так, что действия с самим объектом в большинстве случаев его не трогает. Это как раз в Java есть постоянная работа в фоне с RTTI.
Если к объекту нет обращений чем виртуальный интерфейс, dynamic_cast или нет обращений к виртуальному базовому классу - ничего не происходит.
У тривиальных типов со стандартной моделью памяти RTTI отсутствует.
Это избавляет от необходимости писать весь этот код для самописных контейнеров
кого избавляет, разработчика стандартной либы? Давайте будем честны, мы чаще используем контейнеры, чем пишем свои. Я не против внешних универсальных функций, но почему бы их не продублировать в стандартных контейнерах?
Just use c++20 and requires
согласен, выше по коментам уже указали на этот косяк статьи. Однако все равно SFINAE с нами, и я считаю это одним из самых плохих архитектурных решений что я видел
Нельзя.
можно. Например, передав заранее счетчик ссылок до работы конструктора
Проблема-то где?
вот здесь: "Ну да, это приходится писать "
Дебаггер только чутка тупой
:))))
Однако код
"abrakadabra" == "abrakadabra"
вернет false, ведь это два разных массива, их адреса конечно же не равны.
Результат "abrakadabra" == "abrakadabra"
формально не определён, но на практике в любом вменяемом компиляторе будет равен 1, так как дублировать константу нет никакого резона.
std::string
нельзя сделать строками по-умолчанию в С++ не только из-за обратной совместимости, но и потому что он использует динамическую память, и вообще его поведение недостаточно прозрачное и низкоуровневое для некоторых приложений, в частности для программирования микроконтроллеров с малым объёмом оперативной памяти, где динамическое освобождение памяти часто недопустимо. Возможно, стоило бы облегчить использование std::string
, добавив специальный формат строковых литералов, например 'строка' вместо "строка". А код"abrakadabra" == "abrakadabra"
во многих случаях вернет true из-за объединения одинаковых строк компилятором и линкером, хоть это и UB.
добавив специальный формат строковых литералов
Есть уже 10 лет как. Пользовательские литералы объявлены в стандартной библиотеке и "abrakadabra"s это std::string, если есть соответствующий using.
https://en.cppreference.com/w/cpp/string/basic_string/operator""s
Вообще уже довольно давно существует std::string_view
- являющйся по сути полным эквивалентом char *
, но в то-же время являющийся классом. Но и с ним, есть проблеммы...
Можете пояснить, что именно является UB? Сравнение 2ух строковых литералов на C++?
Как я понимаю может быть либо true, либо false. Откуда UB?
Многие компиляторы могут делать одинаковые строковые константы одной константой, делая одинаковыми все ссылки на неё. Это делается в первую очередь для экономии памяти. Такая оптимизация включается и отключается специальной опцией компиляции. То есть по тексту программы нельзя сказать, будет ли результат true или false - это зависит от применяемого компилятора и его опций.
С одной стороны это хорошо, ведь это можно эффективно использовать как ключ с эффективным сравнением - нам не нужно проверять две строки посимвольно, достаточно сравнить указатель.
ну, вообще-то нет...
В целом горячо согласен, особенно про нестандартную Стандартную библиотеку. Руки бы за неё вырвать.
Но вот про строки согласиться не могу. Во-первых, язык, в котором "abracadabra"
превращается в экземпляр string
должен постесняться использовать букву C в названии (C#? Ну хорошо: язык без виртуальной машины). Во-вторых, это поощряет конкатенацию. А зачем? Нам нужно больше форматирования (конкатенации приводят к ужасающим ошибкам, самая очевидная из которых — внезапная невозможность локализации в культуру с необычными, или просто неанглийскими, грамматиками). В-третьих, это вообще поощряет использование строковых литералов. Это хорошо в хелло-ворлдах, но ужасно в промышленном коде. Ресурсы! Только ресурсы!
(Помню один проект, где использовался макрос _S()
. Разумеется, пустой. Самописный препроцессор вытаскивал все литералы, сводил их в таблицу, потом был GUI-тул для правок… генератор локализованных бранчей… короче, много всякой наркомании. Вот что бывает, когда не осилил StringTable. Компания, между прочим, очень крупная и широко известная в узких кругах).
Помню один проект, где использовался макрос
_S()
. Разумеется, пустой. Самописный препроцессор вытаскивал ...
Нечто похожее есть в UnrealEngine. Там есть кучка пустых макросов, типаUCLASS
, USTRUCT
, GENERATED_BODY
, которые предназначены исключительно для поедания отдельным инструментом под названием Unreal Header Tool, написанным, к слову, на C-sharp. Он по ним еще кучу С++ кода генерирует, который GUI рисует, позволяет взаимодействовать со сборщиком мусора, Blueprint Editor-ом и т.п.
Ну и в плюс кисходному топику. В том же UE есть своя "стандартная" библиотека, частично оборачивающая функции из STDlib - видать не хватает стандартной.
(любопытно, а почему не получается вставить символ решетки)
В этом может быть какой-то смысл. А вот использовать исходный код как первичку для локализуемых текстов… Раскидывать их повсюду, чтобы потом тулзой собирать… Вообще, я заметил, многие не врубаются в идею изолированных ресурсов. Даже великий (кроме шуток) Эрик Липперт.
D UE это не для локализации. Это для визуального проектирования и генерации кода с сохранением привязки к ключевым вещам. Изолированные ресурсы падают лицом в грязь как только встает проблема переносимости или смены версий.
Даже в Qt есть такая проблема и в результат все средства были выкинуты на помойку и теперь библиотека требует чтобы исходный код со строковыми константами был в Unicode. И исходник используется в качестве первички для создания "изолированного ресурса" словаря.
Это, кстати, интересный вопрос. Какие есть варианты создания кросс-платформенных ресурсов в проектах на C++? Чтобы они компилировались (по типу .rc → .res → .dll) и универсально грузились. Всякие диалоги, понятно, не нужны, т.к. сильно зависят от ОС или UI-движка, но разные картинки, HTML, таблицы строк, юникодные тексты и т.п. очень даже универсально востребованы. Да хотя бы вот, Доксиджен после обработки во что собрать? Чтобы одним файлом (и это был НЕ гигантский HTML с инлайновой графикой :).
Я согласен с автором, что астрокомитет занимается чем угодно, кроме того, что нужно в реальной жизни, так что вся надежда на опенсорс.
Для обертки в UE те же причины что и у QT - не все целевые платформы имеют все в реализации.
Вот что бывает, когда не осилил StringTable.
А можете пояснить как совместить StringTable с модульностью?
Если выносить всё строки в ресурсы то становится недостаточно включить/исключить заголовочный файл, надо ещё где то хранить список строк этого модуля и добавлять/удалять их в ресурсах.
PS: это не возражение - реально пробую осилить StringTable.
Буду рад поделиться своим скромным опытом.
Есть StringTable как Идея из платонова царства (именно её я и имел в виду). А есть несовершенные приближения конкретные реализации идеи StringTable — например, дотнетная или винапишная. Видимо, об опыте борьбы с этой последней и стоит рассказать.
У нас было два пакетика травы два модуля на C++, модуль с бизнес-логикой на Delphi и само приложение тоже на C++. (Связывалось всё через COM). Это, КМК, наиболее общий случай «сборной солянки». Мы создали пустую dll'ку (без кода), и все ресурсы, включая таблицу строк, добавляли строго туда. Потому что resource hell, с которым я столкнулся на предыдущем проекте, требовал нормализации.
(Вообще, dll — неплохой контейнер, если писать строго под винды. В WinAPI есть весь набор функций по работе с вложенными в него ресурсами и файлами, а ещё в виндах есть протокол res://
, и все, кто в него умеет, работают с содержимым dll как с обычной папкой. Можно, например, упаковать туда целый сайт со справкой в HTML).
В проекте этой бескодовой dll был rc-файл (и отдельными файлами — все включённые в него внешние ресурсы) и соответствующий ему хедер. Тут возникает первая проблема: их надо всё время синхронизировать и исключить коллизии. Да, это проблема. Но это проблема не StringTable как Идеи, это проблема студии как инструмента. Она не поддерживает сквозную нумерацию, aka autoincrement unique (для этого надо последнее число хранить отдельно, а не рассчитывать его на основании имеющихся), и она не поддерживает автоматическое удаление записи #define IDS_ANSWER_TO_THE_ULTIMATE_QUESTION 42
из хедера при удалении соответствующей строки из .rc. Решение? Надо не полагаться на сломанный WYSIWYG-инструмент (редактор ресурсов), а работать с обоими файлами через редактор текста.
После компиляции мы получаем dll + хедер к ней для загрузки строк и других ресурсов. Порядок сборки гарантирует, что ресурсная dll'ка соберётся первой и будет положена куда надо (в папку с exe'шником), а хедер легко расшаривается между С++-проектами просто как файл.
Хуже всего, конечно, было дельфишнику, который не мог просто включить сиплюсплюсный хедер в проект (из-за разницы в синтаксисе) и ему надо было в pre-build steps конвертировать его в паскалевский файл (это тривиально).
В итоге — полное отсутствие resource hell (включая сюда гарантии, что у тебя абсолютно точно нет дубликатов или нелокализованных строк), встроенная локализация и мегаудобное сопровождение (двадцать лет спустя я безо всяких исходников беру простой Resource Hacker и смотрю фрагменты HTML, которые когда-то использовал, или могу что-то исправить для конкретной площадки).
На примере винапишного StringTable я, КМК, показал, какими принципами мы руководствовались. А что сейчас в этих же целях имеется для кросс-платформы, и имеется ли что-нибудь вообще, или надо ручками писать — самому интересно. Но я лучше напишу ручками загрузку ресурсов из контейнера, чем буду иметь дело с окрошкой из кода и данных.
необходимость передачи .begin()/.end() в большинстве случаев.
std::ranges::find_if(vec, ...)
и другие функции в std::ranges:: хедера <algorithm> решают эту проблему.
Но вот про extension methods согласен частично, ибо возможность сломать что-либо всё же присутствует, хотя это можно решить через добавление в стандарт чего-то вроде using-ов по типу того, как это происходит со строковыми и числовыми литералами. Тут мне нравится предложенное кем-то использование через оператор ".*", но странно, что его так и не приняли в стандарт. Ну, если писать свою библиотеку, то можно попробовать перегрузить оператор "->*" в качестве замены (всё равно по назначению его никто не перегружает), но проблему уже имеющегося кода это не решит.
Про модули и requires уже написали, так что распинаться не буду.
Их относительно легко использовать, особенно если использовать литералы.
Ну, тут немного моей личной фантазии, но если бы в c++ были бы shared immutable-строки как во многих языках на уровне хотя бы библиотеки, это было бы довольно хорошим решением, ибо можно было бы на constexpr-уровне сделать что-то вроде ""sv, но с возможностью конкатенации и другими плюшками.
Ну а так, лучше всего всегда писать ""sv тли ""s по ситуации. Но вообще, проблема первого в том, что операции конкатенауии и подобного недоступны, а воорого в том, что он всегда аллоцирующий.
Можно, конечно, написать нечто вроде ""s + "..."sv + some_fun(), но это не совсем то.
Ну или, к примеру, условно:
constinit static s = "abc"sv;
s+= "def"
Не откомпилируется
shared immutable строки в C++ это разве не std::string_view?
Для поиска в данном конкретном примере можно пойти ещё дальше, и использовать find и проекцию:
std::ranges::find(myVec, tag, &GameObject::myTag);
А что, там не опций какой вариант стандарта использовать при компиляции? Для старых проектов один, для новых - другой?
У некоторых перечисленных проблем - есть решения. Ну типа как с инклудами и c++20 стандартом. Ну или например:
А если в конструкторе нужно создать некую древовидную структуру родитель-ребенок, где дети хранят слабую ссылку на родителя?
Для этого наследуемся от : std::enable_shared_from_this. Внутри вызываем shared_from_this. Вопрос лаконичности - тут субъективно. Но то, что нужно делать дополнительные телодвижения для типовых ситуаций - это факт.
Вы сначало напишите тестовое приложение по вашей идее а уже потом советуйте. Шаред фром зис НЕ решает проблемы получения шаред указателя в конструкторе. Я вам по секрету скажу у этой проблемы НЕТ решения если есть требование использовать стд шаред поинтер. Если свой шаред поинтер написать то можно решить эту проблему, но со стандартным никак.
В случае с геттерами, сеттерами. Их не нужно городить в каждом классе. Они нужны только для того, что если у вас в классе должен сохраняться инвариант, и чтобы пользователь не мог его нарушить. А теперь такой вопрос, какой инвариант может нарушиться если мы доступ к position сделаем public? Что можно сломать? И зачем тогда нужны методы get/set?
Что можно сломать?
Например, запихать NaN.
Польза от них в том, что можно поставить breakpoint и ловить использования/присваивания.
В современных IDE можно поставить дата брейк на любую нужную переменную.
Скажу страшную вещь, лично я включаемое пользователем исключительное состояние по доступу к переменной первый раз увидел в императивном как доска языке PL/I в 1989 году.
Это не эквивалентно. Дата-брейкпоинты полезны чтобы поймать изменение конкретной переменной/поля. Брейкпоинт в сеттере позволяет поймать изменение этого поля в любой структуре. В зависимости от ситуации может быть нужно как одно так и другое.
К сожалению люди их городят (и стайлгайд может требовать так делать). Потому что, если в будущем захочется заменить поле на пару геттер-сеттер, то внезапно понадобится менять весь вызывающий код.
А если в языке есть удобные проперти, то замена поля на проперти делается безболезненно и её можно сделать только когда она реально понадобится.
Пример из соседних языков - в java очень часто пользуются геттерами и сеттерами и пишут кучу бойлерплейта, в Kotlin и Scala используют поля и при неободимости (по факту, довольно редко) точечно заменяют их на проперти.
В C++ есть правило resource initialization is resource acquisition. Идеология в том что параметры должны передаваться в конструкторе класса и потом создавать экземпляр. А не как можно/молодёжно в C - сначала создать экземпляр а потом на 30 строчках кожа устанавливать его значения.
Если вас такая идеология не устраивает то претензии к Автору языка. Это не баг—это фича
resource initialization is resource acquisition
Вообще "resource acquisition is initialization". И RAII — это не правило, а идиома — никто никого не заставляет это использовать. К тому же идеология RAII совсем не в том, что вы описали. RAII не про передачу параметров в конструктор, а про то, что некоторый ресурс привязан к жизни созданного объекта, и, например, при уничтожении объекта, ресурс высвободится/закроется/уничтожится.
Причем тут "автор языка", и как это все относится к комментарию, на который вы ответили — загадка.
Вообще-то: Other names for this idiom include Constructor Acquires, Destructor Releases (CADRe)[9] and one particular style of use is called Scope-based Resource Management (SBRM).[10]
Если вы не передаёте все параметры в конструкторе, то скорее всего resource acquisition случается в коде. Что нарушает идиому.
Как писал в качестве примера C sharp и иже с ним где после создания объекта 30 строчек инициализации
Из опыта работы с __property в Билдере - эта самая "проперть", внешне выглядящая как переменная, на самом деле таковой может не быть совсем. Самый простейший пример - параметр в ini файле. Который читается геттером и записывается сеттером. Или еще что-то более сложное.
есть пример в статье - это изменение позиции гейм-обжекта. После ее изменения нужно пересчитать матрицу трансформации. То есть для установки позиции должна быть вызвана функция
Проперти спокойно пишется. Просто вы не пробовали.
Ну почему же, пробовал. Вот как используется. Пока что это лучшая реализация, которую я получил. Но она всратая - хреновый синтаксис в виде макроса, поле проперти хранит минимум один указатель внутри, генерится туева хуча микро-классов, и я надеюсь что их оптимизирует компилятор. Но нужный функционал дает
Другие реализации либо хуже по памяти, либо по производительности.
Но если бы это работало на уровне языка, никаких этих минусов не было бы. Конструкция object.position = object.position + other.position
; заменялась бы просто на пачку сеттеров и геттеров
Я вас немножечко удивлю, но если из класса торчат публичные переменные, то либо вам не нужен класс, либо вы лени.... Чаще всего второе. Смотришь потом на этот код, и руки оторвать хочется
Метод гет сет на чистые данные без предварительной обработки это нарушение инкапсуляции. В такой ситуации скорее всего дизайн класса сломан. На счёт инварианта очень легко вообразить что в процессе эволюции проекта нам потребуется менять поведение объекта например чтобы он перемещался только в определённой зоне или не мог уйти в минуса по положению. И тогда паблик свойство сделает очень больно тому кто будет это рефакторить под новые требования.
Мне не нравится подход к критике, который использует автор в данной статье. Возникает ощущение, что по мнению автора все описанные проблемы - просто результат произвола в дизайне языка, который был допущен когда-то какими-то комитетчиками. В реальности же многое сделано так и никак иначе вполне по понятным соображениям - чтобы обеспечить обратную совместимость, гибкость и т. д.
Предложенные фичи properties и extension methods - вообще попытка внести я язык то, что ему чуждо. Тривиальные геттеры/сеттеры - это антипаттерн, а методы вне класса - это потенциальный выстрел в ногу.
Автору вообще не интересно ни в чём разбираться. Просто, "мама, хочу", и всё.
Но тем не менее, лишний сахар ещё никому не вредил. Методы расширения - это отличная идея, как и рефлексия, которая через одно место уже частично есть. "Методы вне класса" уже есть в плюсах в гораздо худшем и стрелябельном виде - в виде операторов (особенно красиво можно отстрелить что-нибудь операторами приведения). Так что методы расширения не несут никаких угроз
Большая часть разрабов на плюсах просто не стали учить других языков, решив, что выученных плюсов в процессе обучения в унике им вполне хватит на все возможные задачи
Как говорится, когда у тебя в руке молоток, все вокруг кажется гвоздями
"Большая часть разрабов на каком языке" учит все другие ЯП? )
пока что по моим личным наблюдениям, не претендующим на репрезентативность, разработчики на C++ писали или хотя бы пробовали гораздо больше других языков чем программисты на Csharp(я не понимаю почему мне редактор не позволяет вставить решётку) или js, как минимум по тому что без этого в принципе трудно жить в C++ мире, это прям нужно постараться чтоб не знать ни одного другого языка
Всё просто. После шарпов уже не охота уходить на другие языки - удобный синтаксис, тулинг и если знаешь как, то можно писать zero-allocation код.
Но если серьёзно, то именно плюсы вызывают наибольший взрыв мозга у шарписта – почти тот же самый синтаксис, а тривиальные и обычные вещи делаются так, как будто язык делали инопланетяне.
у меня аналогично с шарпом, вроде синтаксис похоже на C++ но куча мелких вещей которые меня раздражают типа как понимать я данные по ссылке или по значению передаю, почему я не могу написать просто свободную функцию без заварачивания её в класс. Ну и самое главное меня очень раздражает что есть ещё какая то ненужная мне прослойка между моим кодом и ОС.
Мне кажется, что это всё психология. Я когда с шарпов на плюсы переключаюсь, почему-то сразу начинаю думать о том, как писать код супер-пупер оптимально.
То есть я бы рекомендовал бы вам просто не париться над этим вопросом, особенно с учётом того, что в 99% передача идёт по ссылке и это определяется типом аргумента(class/struct).
Свободная функция - это типа просто функция в глобальном неймспейсе? Типа как main в C?
Соберите в NativeAOT и считайте, что прослойки нет. Только поток с GC :)
То есть я бы рекомендовал бы вам просто не париться над этим вопросом
сама мысль об этом мне противна))
типа просто функция в глобальном неймспейсе
не обязательно в глобальном но в целом да
Вот, я же говорил - это всё психология и ощущения :)
О, я понял. Статические классы со статическими методами. Можете воспринимать статический класс, как вложенный неймспейс.
А если писать `using static StaticClassName`, то возникает ощущение, что подключаете header - методы класса можно вызывать не указывая имя класса.
Я так однажды портировал C код в шарп. Каждый .c файл превращался в статический класс, а каждый include превращался в using static.
Properties в C++ есть. По крайней мере, в Microsoft Visual C++
И ведь логично было бы поправить самые крупные косяки, но нет, все заплаточки аккуратно пришиваются сверху чтобы "не дай боже старая всем нужная библиотека не сломалась". Ну раз она старая и всем нужная, разве никто не проапгрейдит ее?
Проблема в том, что это не одна такая библиотека, а они примерно все такие. Переход на новый стандарт C++, временами даже на новую версию компилятора, это довольно нетривиальный процесс если у вас миллионы строк кода временами почтенного возраста. Если эти миллионы строк кода ещё и не у вас, а у пользователей вашего тулкита по всему миру, то становится ещё больнее и дороже.
Добавить сверху "а теперь мы выкидываем это древнее говно" - и вас уже могут просто поколотить.
Много хейта и 0 понимания, почему так получилось. Таких, увы, большинство)
Да кому легче-то от понимания, почему.
Да ну нет, я примерно понимаю почему пришли к таким решениям. Но это не значит что они хорошие. Меня не устраивают текущие решения, я постарался описать свои идеи как это могло быть. Критика - тоже важная часть нахождения консенсуса
Строка в С++ не то чтобы строка:
"abrakadabra"
может быть и выглядит как строка, но в языке С++ это указатель на массив.
Разве?
Тут не суть важно, указатель на первый элемент пусть будет. Тут примечательно другое. Хейтер так спешил хейтить, что почему-то не поделился, как же классно склеивать строки в языках со стринг пулингом, как это правильно и удобно через всякие стрингбилдеры. Не то что в богомерзких плюсах, просто += без раздувания этого самого пула
Тут не суть важно, указатель на первый элемент пусть будет.
И не указатель на первый элемент.
Ну тогда просвещайте, что же представляет собой сырая строка (она же массив) в выражении сравнения? )
Ну тогда просвещайте, что же представляет собой сырая строка (она же массив) в выражении сравнения? )
В исходном фрагменте, на который я отвечал:
Строка в С++ не то чтобы строка:
"abrakadabra"
может быть и выглядит как строка, но в языке С++ это указатель на массив.
нет ни единого намёка "на выражение сравнения".
Ну как я и говорил, вот и страшилки "аллокации массивов" в плюсах в помощь притянуты? А можно немножко глубже в тему, с циферками?
И раз уж вспомнил QString (странно, что CString не вспомнил)... Так какие строки в плюсах самые правильные, чтобы их можно было прятать за кавычками?
Глубже в тему здесь, но, к сожалению, без циферок.
А хотелось бы с циферками, причём в сравнении с каким шарпом или го.
А вы сомневаетесь, что разница будет и заметная, или вам интересен её масштаб?
Сомневаюсь, что разница со стрингбилдерами будет, а вот с += будет не в пользу стрингпулинга
Чем странно?
Тем, что не вспомнил
Не уверен, что зоопарк строк, решающих одну и ту же задачу — это аргумент в пользу того, что плюсы — хороший язык.
Говорите прям как менеджер. В деталях не бывает одних и тех же задач, они все разные. Какие именно строки использовать в том или ином проекте, зависит прежде всего от интеграций и используемых библиотек. Плюсы - хороший язык, потому что там есть много всего на разные случаи жизни. Если нужно что-то одно самое правильное, то перейдите на го. Там вам будут самые правильные строки (серьёзно, без шуток), но со стрингпулингом... и без тернарного оператора... но как только вы попробуете там найти хорошее решение для логгирования, вам опять придётся искать самый правильный язык.
Ну ок, я не против. Мне хватает здравого смысла и экстраполяции опыта.
Нельзя полагаться на ашчушчэния, раз уж тут всё так критично.
А как с
+=
это вообще должно работать? Разве что стрингбилдер обозватьstring
ом.
Не понял, и ладно.
Ну так Qt — единственный адекватный выбор для написания гуёвых приложений на плюсах, в отличие от MFC. Не понимаю вашего сарказма.
Опять вы о своём единственном. Не надо. Тут не сарказм, а небольшое недоумение, почему вы помните про тяжёлые вещи, но не помните про лёгкие.
Говорю как человек, который понимает, что строка — это словарный тип, а словарный тип должен быть универсальным. Собсна, вы даже сами понимаете следствие из текущей ситуации в плюсах:
Не очень понимаю, какой смысл вы пытаетесь в это вложить, но исходя из способов представления одного и того же текста этот тип уже не может быть универсальным.
но, увы, не понимаете причин.
Причины как раз банальны. Начиная от того, что плюсы не принадлежат никому, и заканчивая тем, что каждый хочет что-то своё.
В подавляющем большинстве задач на детали строк (и прочего подобного) плевать. Как много проектов выкинули
std::string
, когда оттуда выпилили COW или запилили SSO?
Возьмите 2 либы с разными типами строк и продемонстрируйте. И ведь у каждого была мотивация использовать именно то, что они используют.
Конечно, есть контексты, когда строки (хэшмапы, етц) нужны какие-то свои. Но они исчезающе редки на фоне средней софтварной разработки. В плюсах зоопарк строк не потому, что у всех офигеть какие особые детали, а потому, что штатные строки омерзительны, и до C++20 (или 17? не помню уже) там даже для проверки префикса строки надо было поприседать.
Они не омерзительны, просто 1) так сложилось исторически, 2) в который раз, нет самого правильного решения. Третий пункт озвучивать не буду. А будете спорить, то натравлю злого бустовика. Он вам расскажет про идеал в мире плюсов ).
А прикиньте, если бы инты были разные ещё, и в каждой библиотеке свои? А чего, кому-то нужно переполнение с насыщением, кому-то с экзепшоном, кому-то надо бигинт. Шикарный бы вообще язык был, ящетаю!
А прикиньте, они уже разные. Знаковые, беззнаковые, непонятнознаковые (привет от char), разной разрядности, littleendian, bigendian. Шарпового decimal очень не хватает, кстати ). И зачем это всё? Просто всегда юзайте int128, да и всё.
Я на хаскель перешёл, спасибо, мне хватает. У меня есть
Data.Text
(и ленивая версия) для уникодного текста, естьData.ByteString
(и ленивая версия) для строкоподобных массивов байт, иString
, когда я ленюсь писать аннотации типов и играюсь в репле. Для 99.9% задач этого хватает.
Ну почему надо сразу вспоминать какую-то гиковскую дичь. (ой, а я на эрланг перешёл). И ведь даже в ней у вас никакого единства. Строки такие, строки этакие, ленивые версии (вам привет от плюсовых вьюшек в этом месте). Вот в го совсем один тип. Вообще. Один. Как вы и хотели.
В какой доле задач вам на самом деле важно, как представлен текст?
так дело не в процентной доле, а в том что она принципе есть.
views::iota
пускай меня захейтят поборники функциональщины, но я хоть убей, не понимаю на кой ляд было в принципе добавлять эту дичь как и все ренжи, есть старый добрый for(int i = 1; i < 10; ++i), а всю эту дичь функциональную пускай используют те у кого нормальных циклов нет.
В чем стрёмность то, вы поборник тоталитаризма? я за свободу выбора строк))
Да и в целом если уж говорить то в самом языке строк и нет, есть целы типы и массивы всё остальное это лишь часть библиотеки которая не обязательна для использования. И нЕчего для сущности которую можно представить массивом целых чисел вводить в язык специализированный тип.
find_if(iota(curIdx + 1, count - 1),
и зачем мне такую дичь писать когда есть номальные циклы в которых можно и точку останова поставить и в отладчике поитерироваться
std::optional maybeIdx;
for(auto i = curIdx + 1; i < count && !maybeIdx; ++i){
if(isImportant(i)) {
maybeIdx = i;
}
}
И вы правда вместо каждого поиска/фолда/трансформа вот так циклы расписываете?
Какие нибудь? весь этот код одна сплошная ошибка, что за getCurIdx(), count() select(*pos);
откуда эти глобальные функции и на что они ссылаются? из чего и что мы вообще выбираем и зачем в onClick?
void передаём? с удовольствием напишу ответ на код хотя бы отдалённо приближенный к реальности но не на это недоразумение
и ты же в курсе что ренжи не бесплатные (https://youtu.be/cK4cMdx9QeQ?si=tEoYzGKi9OMv7zxS) ? зачем мне платить за то что выглядит уродливо да ещё и ухудшает читаемость кода?
Hidden text
struct Button {
void onClick(std::function<void()> cb);
};
class MailView {
static bool important(int idx);
static bool unread(int idx);
static bool hasReplies(int idx);
void setupUi() {
findNextImportant.onClick([this] { getNext(important); });
findPrevImportant.onClick([this] { getPrev(important); });
findPrevUnread.onClick([this] { getNext(unread); });
findNextUnread.onClick([this] { getPrev(unread); });
findNextReplied.onClick([this] { getNext(hasReplies); });
findPrevReplied.onClick([this] { getPrev(hasReplies); });
}
void getNext(auto pred) {
for (auto i = getCurIdx() + 1; i < count(); ++i) {
if (pred(i)) {
select(i);
break;
}
}
}
void getPrev(auto pred) {
for (auto i = getCurIdx(); i >= 0; --i) {
if (pred(i)) {
select(i);
break;
}
}
}
int getCurIdx() const;
int count() const;
void select(int);
private:
Button findNextImportant;
Button findPrevImportant;
Button findPrevUnread;
Button findNextUnread;
Button findNextReplied;
Button findPrevReplied;
};
уу целых 8 строчек добавилось 6 из которых новые скобочки, это ни о чем даже при отсутствии в примере хоть какого то действующего кода по мимо обвязки. А когда он будет то эти 8 строк в файле на 300 даже не заметить, вот только код стал читабельнее и проще в отладке, не требует от джунов понимания каких то iota и прочей функциональной дичи, легко кастамизируем, ведь в любой момент может оказаться что итерироваться нужно не просто от А до Б с шагом 1, если окажется что предикат не просто возвращает true/false а длину следующего прыжка например.
А если ты всерьёз считаешь что твой вариант с кучей лямбд к месту и не к месту со странными областями захвата, читабельнее то мне жаль коллег которые с тобой работают.
А касаемо потери zero cost, увы но да, этим мне и не нравится то куда сейчас движет C++ с этим помешательством на расширении "функциональщины", так как это будет провоцировать появлению вот таких вот повернутых на однострочниках, которые потом фиг разгребешь на проде.
И в которых вы умудрились сделать ошибку
сорян но это не у меня ошибка, а проблема твоего "очень понятного кода" и то как я его понял
Джунов к плюсам вообще пускать нельзя.
тогда откуда по твоему берутся синьёры и мидлы? cpp в этом ни чем не хуже и не лучше других языков
Что странного?
а на кой тебе захват всего по значению?
так а что на счет когда предикат будет возвращать длинну перхода, как твоя iota и revers c этим справится?
И ещё
count
на каждый чих вgetNext
вызываете, хотя заботились об оверхеде рейнджей, и я что-то не думаю, что компилятор тут осилит этот захойстить вне цикла.
с чего бы вдруг то? да и мне откуда знать что делает count и не меняется ли случайно значение из другого потока и нужно ли сохранять это поведение, а если потребуется то твоя iota опять в проигрыше так как если меняется и сохранять не нужно то для оптимизации мне достаточно её самому вынести за цикл, а вот если меняется и сохранять нужно "внести" в iota будет веселой задачкой.
А если
getCurIdx
бы возвращалunsigned
, ух…
то мне нужно всего лишь поменять 1 строчку в коде
А если бы я ваш код не так прочитал, то это была бы моя проблема
в моём коде разберется любой школьник, он не требует понимания пайпов и ренжей, о чем могут быть не в теме даже многие олдовые программисты на C++
Либо из других языков...
Сам начинал джуном на C++ и сейчас иногда нанимаем джунов на С++, ни чего уникально в этом языке нет, хватит пропагандировать этот бред про невозможность начинать проф. карьеру на C++
На порядки хуже из-за своей сложности.
Сложности с ним начинаются по мере роста задач и обобщённости кодовой базы, пока человек пишет под одну платформу, 1 тулчейн и продуктовый код что обычно и делают 99% программистов на других языках он мало чем отличается от шарпа или жабы. Либо благодаря вот таким фанатикам впихнуть все фичи из самого последнего стандарта ради того чтоб сэкономить 4 строчки и навернуть шаблонного мета программирования там где и без него отлично все обходится.
в которых, очевидно, хранится содержимое вьюшки
очевидно кому? будь больше деталей то возможно бы я и написал по другому, а вот как ты собрался сопровождать код на 300+ строк кода в котором все зашито в лямбды с неочевидными зависимостями в стеке фнкции, в место того чтоб разбить это все на пару тройку методов пусть и с небольшим дублированием я бы посмотрел
С чего бы это ему возвращать?
с того что это возможно, и даже на этот синтетический пример можно натянуть, например если у нас письма стакаются в цепочки и мы определили что это не isImportant
то сразу можем вернуть сколько следующих пропустить чтоб их не проверять. Но это вообще не важно, важно то что применения iota очень ограничен и в 99.999% может быть спокойно заменен на обычный for.
Если бы оно менялось, то, очевидно, для доступа к каждому элементу нужна была бы синхронизация
а у нас thread safe контейнер например
какую?
вот эту, заодно и ошибку с недопониманием поправлю
for (auto pos = getCurIdx(); pos > 0; --pos) {
auto i = pos - 1;
Отлично. А в одной крупной компании, где я работал, ровно поэтому было рекомендовано не использовать unsigned-типы (и они были забанены в основных библиотеках) — потому что при оставлении >= там легко получить бесконечный цикл.
отвратительно. Как раз unsigned выражает в типах что число неотрицательное и не содержит уб на переполнение, всё как вы любите. А на >=
выдаст варн "expression is always true".
ну давай подумаем почему конкретно в этом примере я все же могу утверждать что мой код проще. циклы и ветвления проходятся на самом первом занятии по информатике, for один из самых распространенных среди всех языков, а на каком уроке проходят ренжи, пайпы и iota в частности не подскажешь?
в одной крупной компании, где
так это ты задался вопросом что если, а не я.
а использование goto избегается по вполне объективным причинам не имеющим ни чего общего с for и iota.
эта мантра про отстрел ног порядком задолбала. в других же языках ни багов ни проблем с потоками и утечками ни когда же не бывает, да?
просто беру и сопровождаю.
можно весь проект в одну функцию main запихнуть но почему то считается плохим тоном
глядя на
iota
, вы точно знаете, чего он не делает
проблема в том что я не ебу что она впринципе делает, что у нее за тип, какая стоимость, а если ее нужно создавать миллион раз в секунду, а передавать дешевле по значению или по ссылке, включает границы или нет, а что происходит с границами при reverse ни на что из этого нет ответа из "простого и локаничного iota(a,b)"
Покажите неуродливый читаемый код, аналогичный этому
Так что?
Допустим, в первый год обучения в школе лично я проходил рекурсию, а не цикл с управляющей переменной, так что теперь? А на подробное объяснение студентам семантики цикла с управляющей переменной у меня уходит три часа.
когда ты лид команды разработчиков то ориентируешься на большинство, а не одного конкретного разработчика который по каким то странным обстоятельствам начал программирование с функциональщины
О! Мы подходим к реальным обстоятельствам. Булыжник - оружие пролетариата. Это точно подмечено.
Программирования это про командную работу и решение бизнес задач, по крайней мере для меня, а весь этот мысленный онанизм над очередным сферическим конем в вакууме мне совершенно не интересен и рассуждать в этой плоскости для меня не представляет ни какой практической ценности.
Решение бизнес-задач не обязательно производится силами первых попавшихся выпускников церковно-приходской школы. Так вообще опасно делать в некоторых случаях.
к чему тут апелляция к первым попавшимся, я не понимаю ради чего усложнять код на ровном месте. Или перейдем к спору про принцип KISS?
У разных людей могут быть разные взгляды на то, является ли цикл с управляющей переменной упрощением кода iota.
То как было использована иота в изначальном примере - это явно усложнение потому что там рэнж создавался отдельно от логики его использования. Это дополнительная абстракция. Если не нравятся операторы сравнения напишите for (auto i : iota(0, n))
Имхо в таком примере это преждевременное создание абстракций, получается усложнение. getNext/getPrev выглядит проще и понятнее. К тому же это ближе к реальному функционалу юай - там есть кнопки prev/next и у вас в программе будут такие методы, a не какой-то findAndSelect(forward).
Читать одинаково нормально. Использовать getPrev() как одну сущность проще чем писать get, а потом думать какой итератор надо туда вставить. Решение с ренжем в качестве аргумента даёт большую гибкость тому кто будет использовать код, с другой стороны накладывает больше мыслительной работы. Абстрагировать надо в точках где ожидается вариативность. Если там prev/next/left/right/up/down/diagonal/whatever то это другое дело.
А думать о том, куда надо get делать, вперёд или назад, не надо?
Не надо, это закрытое множество вариантов.
Вообще интуитивно у меня будет отдельно контроллер списка, и отдельно несколько способов его вызывать. Скорее всего из разных диалогов. Например, какой-то общий диалог поиска писем и спец кнопка перейти к следующиму непрочитаному во фрейме отображения письма. И тут иметь готовый findNext(pred), который я могу переиспользовать лучше чем findAndSelect.
Плюс, theorems for free и всё такое.
Во-перых это не имеет никакого отношения к С++. Во-вторых что тут это даёт?
Вариативность вперёд-назад уже достаточна, как по мне.
как по мне для того чтобы объединить две строчки (find_if + select) в новый метод этого не достаточно.
могут, но таких меньшинство, по крайней мере в си подобных языках и уж тем более среди C++ разработчиков. мы же не про сферических коней в вакууме говорим, не так ли?
А на каком уроке проходят передачу функций в функции и параметрический полиморифзм? Почему вы этим воспользовались?
на том же что в вашем примере, я не использвал ни чего что бы добавляло сложности и новый конепций в уже написанный код и параметрический полиморифзм не являлся темой дискусси, не нужно прыгать с пятого на десятое.
ту же фундаментальную подоплёку
в чем она та же? goto используется для безусловных прыжков, у него такая же схожесть с for как у рекурсии с циклами, какой то аргумент притянутый за уши. И до сих пор не понимаю к чему.
Их там на порядки меньше.
хотелось бы посмотреть на эту статистику взятую с потолка
Это немного не тот же уровень сложности
а я тут и не про iota говорил, а вообще про кучу лямбд
Эти вопросы в подобных случаях можно не задавать
а можно и задать
Сделали неловящуюся тайпчекером
тз не было, был не компилирующийся код с не очевидным поведением, как смог так и понял уж извини.
но ожидаете от всех предикатов одинакового поведения.
Предикаты у вас статические функции, хотя э
я ни чего не ожидаю, можешь их хоть обратно в лямбды запихать это вообще предметом спора не являлось и мне было просто в падлу их переписывать в псевдо коде или думать что там в них находится и писать нормально.
for (auto pos = getCurIdx(); pos > 0; --pos) {
auto i = pos - 1;
//
}
Имхо такой вариант самый простой
for (auto pos = get_current_idx(); pos-- > 0;) {
//
}
Ренжи по сравнению ч циклами дают инверсию контроля
старый добрый for(int i = 1; i < 10; ++i)
у кого нормальных циклов нет
Так этот старый-добрый как раз и не является нормальным циклом. Не каждый осилит с первого раза правильно написать цикл от a до b включительно с итератором того же типа, особенно если шаг цикла больше 1. В то время как даже в древнем паскале это не проблема вообще.
не каждый осилит с первого раза правильно написать цикл от a до b
мне кажется что для тех кто не осилит, программирование не лучший выбор профессии
Ну да, но часто ли в C/C++ вы видите циклы for со счётчиком, в которые приходят заранее неизвестные границы, и которые написаны с правильной обработкой граничных случаев типа b == INT_MAX? Лично я ни разу не видел такого.
Справедливости ради, в Паскале граничный случай b == INT_MAX тоже приводит к неопределённому поведению. Эта проблема имеет естественное решение только в Фортране, где циклы по своей семантике вообще устроены хитро. И ещё в языке PL/I специально для этого придумали UPTHRU.
Ну, понятное дело, есть ещё языки, где нет максимального целого.
Первый раз слышу, чтобы были проблемы в паскале с границами циклов. Вот такой цикл for в Си так просто не написать: https://onlinegdb.com/8SIcszwxI, потому я бы не стал называть его "старым добрым". Старый - да, добрый - ни в коем случае.
Стандартом языка Паскаль не гарантируется, что такой цикл закончится для всех типов. И я видел компиляторы (например, Virtual Pascal, VS Pascal), где для максимального целого он становился бесконечным. Современные компиляторы Паскаля де-факто решают эту проблему, но никто ничего не обещает.
При этом надо ещё понимать, что аккуратное дельфийское решение не даётся бесплатно. Это лишние машинные команды в каждом шаге цикла, в то время как очень редко нужен цикл до максимального целого. Именно поэтому в PL/I придумали два вида циклов – эффективный и аккуратный.
В стандарте (мы же об ISO 7185?) написано (пункт 6.8.3.9), что for v := e1 to e2 do body должен быть эквивалентен такому коду:
begin
temp1 := e1;
temp2 := e2;
if temp1 < = temp2 then
begin
v := temp1;
body;
while v <> temp2 do
begin
v := succ(v);
body
end
end
end
Как видим, цикл с любыми валидными значениями порядкового типа вполне определён и отлично работает даже с границами, в отличие от сишного for.
Справедливое замечание.
Однако, вы ж понимаете, что на практике ни один компилятор так код не разворачивает.
Кроме того, хотя в Паскале шаг цикла ограничен возможными значениями +1 и -1, но в Си это не так, и поэтому таким трюком не обойтись.
Так этот старый-добрый как раз и не является нормальным циклом. Не каждый осилит с первого раза правильно написать цикл от a до b включительно с итератором того же типа, особенно если шаг цикла больше 1. В то время как даже в древнем паскале это не проблема вообще.
И где тут решили шаг больше 1?
не вижу ни какой сложности в том чтоб в таких очень редких случаях вызвать код ещё раз после цикла или переписать на do while. И честно говоря за 10 лет ни разу с подобными проблемами не сталкивался. Ну и опять же iota на сколько знаю не решает эту проблему.
вызвать код ещё раз после цикла
Всё тело цикла ещё раз продублировать? Так себе решение, сопровождаемость на нуле.
или переписать на do while
Чем он поможет, если do while по сути эквивалентен for? Та же самая проблема будет на граничных значениях.
auto i = std::numeric_limits<int8_t>::min();
do {
printf("%d\n", i);
} while(i++ != std::numeric_limits<int8_t>::max());
какие же? ну и вопрос всё тот же, что мешает взять тип больше, а если ты хочешь пойти дальше и поитерироваться от std::numeric_limits<int64_t>::min(); до std::numeric_limits<int64_t>::max(); то ты явно что то не то делаешь в коде или пытаешься решить какую то абстрактную задачу
какие же?
Я сразу предположил, что мы заранее не знаем границы. Отсюда, в этом коде не хватает проверки, что правая граница не меньше левой.
что мешает взять тип больше
Не всегда он существует.
если ты хочешь пойти дальше и поитерироваться от
std::numeric_limits<int64_t>::min(); до
std::numeric_limits<int64_t>::max();
Нет, я хочу проитерироваться от a до b, значения которых мне заранее неизвестны. Может быть, от std::numeric_limits<int64_t>::max()-1 до std::numeric_limits<int64_t>::max().
или пытаешься решить какую то абстрактную задачу
Программистам ли удивляться абстрактным задачам? При том что вся абстракция - это границы диапазона итерирования, определяемые в рантайме.
опять какие то пространные обсуждения в вакууме, пример задачи которая бы не решалась for/while но решалась бы iota или ещё как то
Да ладно вам, постоянно в уважаемом коде не осиливатеся что-то простое, из-за чего кто-то в результате если не null разыменует, то за границы вылезет.
safeIota :: (Ord a, Enum a) => a -> a -> [a]
А где хотя бы случай когда второй аргумет равен бесконечности? Где поддержка operator[i] у хаскель списка?
А где хотя бы случай когда второй аргумет равен бесконечности?
Я ни разу не знаю хаскель, но в нём это добавляется не просто, а очень просто:
data Bounds a = Bound a | Infinity
foo :: (Ord a, Enum a) => a -> Bounds a -> [a]
foo value (Bound bound) | value > bound = []
| otherwise = [value..bound]
foo value (Infinity) = [value..]
p :: (Show a) => a -> IO ()
p = putStrLn . show
main = do
p $ list1 -- prints [5,6,7,8,9,10]
p $ take 4 list2 -- prints [5,6,7,8]
p $ take 10 list2 -- prints [5,6,7,8,9,10,11,12,13,14]
where list1 = foo 5 (Bound 10)
list2 = foo 5 Infinity
Где поддержка operator[i] у хаскель списка?
На месте же
(!!) :: [a] -> Int -> a
(!!) :: [a] -> Int -> a
WARNING: This function takes linear time in the index.
не годится
Что значит не годится? Это же список, а не вектор.
Не сравнивайте свои списки с С++ ренжами
Цпп iota имеет, а хаскель не имеет. Сравнение не уместно. Приходите с хаскель примером когда он это научится.
Все с точностью наоборот.
Бесконечных границ в вашем примере нету. Дженерик кода которы работает с конечной и бесконечной верхней границей в хаскеле нету. Рандомного доступа в хаскеле нету. (да сложность O(n) для рандомного доступа равносильна его отсутсвию)
Бесконечных границ в вашем примере нету.
enumFrom 0 что делает, по-вашему?
подчеркнул важное
Привёл выше (или ниже, лень искать по странице).
Ответил на это выше (или ниже, лень искать по странице).
В плюсах тоже нету, почему вы от хаскеля требуете?
Есть random_access_iterator
концепт
support for constant time advancement with the +=, +, -=, and - operators, constant time computation of distance with -, and array notation with subscripting [].
iota его реализует для интов, и других подобных типов. Вообщем ртфм.
по поводу остальных проблем, добавляете -fconcepts-diagnostics-depth=10
и сразу получаете
error: no type named 'difference_type' in ...
Кстати зачем вы копируете ошибки сюда? Для устрашения? Без подсветки и перехода к коду их никто в реальности не читает. К слову ошибки в С++ дают понятную информацию в которой можно разобраться. В идрисе я получал "can not unify A and B" и вот там действительно сидишь гадаешь чего не хватает
clang++
c какой целью вы меняете компиляторы? Сколько раз в день вы меняете компиляторы для хаскеля?
Вы уверены, что стало лучше?
Да. там с подсветкой в гцц сразу видно где ошибки и а где стэк инстанциирования. Читать всё подряд не нужно, это не художественное произведение.
MSVS
там ошибки полное дно
UI подвешивала при попытке их отрендерить
Ошибки на десят экранов из раста тоже легко получить при желании
если мисматч был в implicit-аргументе, которого не видно при печати по умолчанию.
Сам аргумент было видно. Но это не помогает, там же солвер/матчер чё-то пытается сделать, и что он пытается не выдаётся. Примера не будет, я игрался пару лет назад и уже всё стёр.
не один десяток лет и вкладывают деньги MS, Google, Apple, RH, Bloomberg
это весьма условно. Во-первых там несколько компиляторов, так что уже ресурсы сразу делятся на три. Во-вторых то что в компании много денег, не значит что на компиляторы там выделено больше чем полтора человека. Мсвц стд-либу пилит один человек, и он в ишью на гитхабе сообщил что ему времени не хватает и больше ресурсов на это выделять не планируется. В-третьих десятки лет легаси это минус а не плюс. В-четвёртых там не один С++ а еще пачка языков. Да и вообще там круг задач больше чем фронт языка, к примеру, в случае ллвм это фреймворк для создания других модных языков.
Я бы не пребеднялся, у резерч институтов бюджеты тоже хорошие, и дешевой рабочей силы там достаточно. А задачи стоят просто показать пруф оф концепт.
это перебор и слишком толсто
так а в цифрах есть что то? Вот я открываю это https://projects.propublica.org/nonprofits/organizations/471136085
и тут копейки.
Недавно оказалось что в xz тоже мейнтейнер неактивный был, а потом вместо него подсадную утку поставили. Хотя либа использовалась много где я думаю.
И какой размер команды не подскажете?
Больше троих на что? Там у аппл есть и swift и obj-c и куча задач по интерации платформы и своего железа. Но допустим даже трое от аппла на шланг фронтенд, еще трое от гугла. Майкрософт не берём у них свой. Итого шесть. Мне сообщили что в кланг фултайм мейнтейнеров около пяти. Выходит что вложения весьма скромные.
Carruth
открыл блог. похоже он занимается каким то игрушками вроде карбон вместо работы
Разные компиляторы находят разные ошибки
cписок диагностик у гцц и кланг плюс-минус один и тот же. Не вижу смысла. В любом случае это просто фича которого другие язык с единственой реализацией лишены. Я считаю что цена фичи себя не оправдывает.
да и компиляция несколькими компиляторами помогает быстрее найти несоответствия стандарту.
Если вы не разрабатываете библиотеку для широкого использования, то в этом ноль смылсла. Цель сделать рабочий проект, для этого достаточно соответствовать одной реализации С++, которая есть в вашем любимом компиляторе, в которой есть еще и различные расширения, которые можно использовать.
Когда как, иногда много раз, когда, например, между проектами переключаюсь,
Давно у хаскеля много компиляторов? Стандарт вы тоже строго Haskell 2010 используете?
среди 180 строк это найти очень легко
гораздо легче чем в ваших страшилках это описывается
Вообще-то выдаёт, и топ-левел-цель, и конкретную ошибку.
увы мне оно абсолютно ниочем не говорило, может какого флажка не хватало в репле (я что должен их все знать?) может какой-то баг.
Куда же у вас поддержка operator[] пропала у iota?
какой-то совсем тупняк. Вы С++ вчера начали учить? Для рандом акссесс итератора нужно реализовывать арифметические операторы, с ренжами аналогично.
Наверное должно быть [a]
вместо a
.
Если плюсы чего-то не могут, то это желающий этого чего-то виноват.
Если хаскель чего-то такого же не может, то это хаскель виноват.
Может или не может в итоге не язык, а код. Язык даёт возможность. Если цпп даёт возможность а програмист ей не пользуется то виноват программист. А если хаскель не даёт возможность вообще, даже если программист и хочет, то виноват хаскель.
random_access_iterator требует O(1) значит для Foo надо реализовать operator+=
. Пока у вас только operator++
то прийдется скакать по-одному.
Я могу глядя только на него сказать, какие эффекты будут у выражения range[10]?
Это уже другая более общая тема и к итераторам, я думаю, не имеет отношения. Тут хаскель не панацея. Как раз в самом интересном случае с много-потоком эффекты работают хуже чем в раст https://www.fpcomplete.com/blog/when-rust-is-safer-than-haskell/
Хотя трюк с ST монадой и rank-2 типом интересный.
Если вам нужен i-ый элемент после a для Enum a, то вы делаете toEnum $ fromEnum a + i
Теперь ясно, мне эти наименования интуитивно непонятны.
Поэтому не надо писать, что iota в плюсах сразу даёт O(1)-доступ
Мы тут опустились на уровень "Давно вы бросили пить коньяк по утрам?".
Если вам вдруг надо обращаться к i-му элементу за O(1) у генератора, то вы делаете что-то интересное, и мне такое тоже интересно!
так тут не генераторы а ренжи. Например взять подмассив какого то массива, поверх transform и перемножить на iota. В С++ вся информация о размере и рандом акссес сохраняется.
сделайте Vector
Это сторадж данных. Не надо завязывать обработку на конкретный контейнер. Что-то слабовато с абстракциями в вашем хаскель, если возникают такие мысли. Растеры хотя бы span предложили бы.
(Int -> a)
это малова-то нужно иметь еще размер или начало/конец. Еще почему только Int, где ваш пример с Foo? Вот еще пример из раста можно вспомнить. Там у итератора есть collect<Vec<_>>()
метод (или как-то так), и если известен размер заранее то там реализована специализация (эксперементальная фича, используется только в стд либе) которая преаллоцирует нужный размер. Можно ли в хаскеле сделать такую специализацию для [a] ?
Я не понял проблему по ссылке, что именно там у них небезопасно. Что шаренные данные могут шариться между тредами
Там претензия что нету Sync/Send трейтов которые говорят можно или нет пересылать структуру данных между потоками. Соответсвенно если в read/writeIORef нету лока/атомика то будет уб.
последних оставшихся оградок в виде документации
о как вы запели, про С++ точно так же можно сказать =)
Если я завожу IORef, то я этим и говорю, что ожидаю, что её будет менять кто угодно.
Это какая-то мантра схожая с unsafe в раст. Написали и всё "мы в домике". Нужны способы абстрагирования и инкапсуляции этого всего, и критерий как раз то насколько они мощные (бч в расте например не очень мощный, для задач перекладывания жсона хватает). Без Sync/Send я не вижу чтобы они были достаточно мощные. Но я в хаскель не специалист.
Не только. Это и вполне себе слайс тоже, например.
Т.е. хаскель Vector это таки span? Что делает V.map f? Он создаёт новый вектор или лениво ходит в старый и применяет f?
Остальные ответы тоже про вопрос об отличии хаскель Vector от С++/rust вектора. Например это
(V.generate len theGen)
Что это сделает, возьмёт theGen
например тот же iota
достанет из него елементы и положит в контейнер? Т.е на лету из iota уже доставать нельзя?
Пример с Foo выражается как Int → Foo
Вы это тоже не выразите не определив что такое Foo + Int
.
мы точно всё ещё про неограниченные последовательности говорим? Но конец тоже можно передать, кто мешает?
Эту всю информацию надо иметь в каком то тайпклассе или нескольких. Все ваши примеры покрывают только очень частные случаию.
fromListN
так это мне никак не поможет. У меня есть функция которая вернула -> [a]
без размера. Я не смогу к ней приклеить fromListN
никак.
Где уб? Там нет уб в плюсовом смысле.
там же чтение/запись памяти из разных потоков. Во что это хаскель скомпилирует? В просто обычный доступ в память? Тогда это уб потому что типичные оптимизации не предполагают это.
Смотрите, span нифига не помогает, потому что я могу сделать
Ага, именно такие примеры вы и приводите как критику плюсов. А когда такие же против хаскеля то вам не нравится :) Ведь незаметить &*list.begin()
можно, а сделать ошибку внутри IO блока никак нельзя.
Но поинт той отссылки к раст Send/Sync не в этом. А в том что в хаскель нету способа выразить нужный инвариант. Либо IO и сразу минное поле, либо не шарим/муваем данные между потоками.
По поводу свёртки мапов, если компилятор может ходить в тело функций и анализировать/оптимизировать цепочку трансформаций, то это хорошо.
По поводу Vector всё же я не очень понял. Что значит создать из чего угодно за О(1)? Если это был слайс другого вектора, то что будет? Или допустим вообще из MVector? Трюкам с ленивостью и иммутабельностью я не доверяю после примера с qsort.
Вы как-то на ходу меняете условие. То вам нужно было создать из списка с известной длиной, то длина неизвестна. Можно как-то определиться?
Оба нужно. Я хочу заучить один collect<>
или ranges::to<>
и не думать когда там в коде у меня известна длина а когда неизвестна. Хочу сделать какой то трансформатор из ренж в ренж, который будет сохранять эту информацию. В вашем же примере мне надо писать каждый раз теперь
EIther (Int -> a) (Int, Int -> a)
матчить его при каждом использовании, а потом заворачивать обратно в Either. В итоге весь код превращается в такой бойлерплейт, с потенциальными проблемами при оптимизации.
STM в вашем мире не существует?
Выглядит как просто примитивы завёрнутые в мутекс, но не как инструмент для определения самих примитив и ограничений на их многопоточное использование. Поправте если он позволяет добится Send/Sync из раста.
Так мы хаскель-код компилятором хаскеля компилируем, если что, а не плюсов.
Во-первых есть ллвм бэкенд. Во-вторых уб легко получить если завязать какую то дополнительную логику на использование этой переменной
case (readIOref x < n)
true => getUnchecked v $ readIOref x
false => _
(пвсевдо код)
Апд.: подправил, случайно раньше времени отправилось
Будет ссылка на область памяти, которая будет удерживать GC от её удаления, даже если исходный вектор сдохнет
т.е. это таки некий тайпкласс у которого может быть несколько реализаций? Ссылка на другой вектор или ссылка на какую-то свою аллокацию?
У меня вопрос не про владение, можно как одну из реализаций сделать слайс который в отличие от std::span делает рефкаунт (например растовый hyper::Bytes
).
Тогда юзайте списки и делайте по ним map, потому что ваш трансформатор, скорее всего, не random access
В цпп сделано, в раст сделано, а хаскель не смог поэтому ненужно. Понятно..
Так вам примитивы писать или целевой прикладной код, их использующий?
И то и то. Что мне запрещает расшарить ST вектор между потоками вместо STM? Как я для своего контейнера могу определить можно его шарить или нет?
Где здесь ub?
Значение поменяется из другого потока и в getUnchecked
будет уже не тот индекс который прошёл проверку строчкой выше, n
это например длина контейнера.
Это который ...
Да это тот самый ллвм который вы использовали чтобы хоть как то сравнятся по скорости с цпп в своих экспериментах. И тот самый который растеры взяли потому что сами сделать свой не смогли.
можете трансформать обычным fmap после этого
Распарсить что делает of
и ;
я не смог, но идею понял. Хотя вместо Functor нужно иметь тайпкласс который умеет как раз в метод который даёт Int -> a
. Т.е. вот это вот всё надо спроектировать и т п. Не рокет сайнс конечно, но и выдавать однострочники за какие то решения это слишком.
Но опять же вот этот Bounded/Unbounded флаг это рантайм флаг. Афаик в расте тоже было раньше fn size_hint()
, а потом решили еще добавить специализацию https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html.
Кстати тут сразу возникает вопрос с ансейф, можно ли доверять хинту len() для создания аллокаций? В хаскель вопрос будет аналогичный.
Нет разницы между своей и чужой аллокацией. GC, все дела.
а как это тогда работает? Я представлял себе класс слайса как набор из (otherVector, begin, end) и оператор[i] который возвращает otherVector[begin+i]. Сам otherVector держится/очищается через GC, а не вручную.
Что не смог?
Я вам миллион примеров уже привёл под каждую из ваших уточнённых задач согласно каждому из ваших пожеланий
Ну я же просил в самом начале, целостный пример который все задачи обобщенно решает, а не какие-то отрывки.
of — это кусок case, ; разделяет бранчи, чтобы написать в одну строчку в репле. Можно было бы
фух, в новом виде наконец хаскель можно прочитать. спасибо
Зачем отдельный тайпкласс для метода, когда можно, блин, просто передать метод?
Что бы трансформ сделать
transform :: Range k a -> a - > b -> Range k b
и сохранить operator[] после трансформа.
Можно сравнить со страхолюдием из плюсов в темплейтах, кстати.
Первая часть это же просто определение двух конструкторов структур которые ничего не делают. В С++ такое писать особо не надо. Что делает deriving Functor мне не понятно.
А хотя я немного подумал и понял, что скорее всего теперь это класс можно через fmap оттрансформировать но нельзя запихнуть в метод который ожидает [a]
. Так что вопрос про transform отменяется.
Система типов запрещает и rank-2 polymorphism. Попробуйте, короче, сами.
Т.е. у нас контекст runSTM который берёт лок на все STM контейнеры? Всё еще не понял почему я не могу ST контейнер передать между потоками, как вообще что то в поток передать? Если через IORef будет уб то как без уб? Как с этим разобраться новичку.
Много компиляторов продублирует load из атомика после проверки?
Так если load() один то всё хорошо, я специально явно два раза его запросил в примере. В сложных примерах вы такую проблему не заметите сразу. Как раз дублировать лоад это будет баг компилятора.
его компиляторы и так не делают кучу оптимизаций, которые они могли бы делать, поэтому более безопасные языки в безопасности!
Оптимизаций которые они делают достаточно чтобы синхронизировать память через атомики и ставить мемори барьеры было обязательным.
Не получилось переложить вопрос на хаскель.
А аргументация будет?
otherVector + begin
А как такое работает? ptr это что? Обычно массив это внтуренний примитив и ссылку на место где лежит элемент взять так просто нельзя. Или язык должен предоставлять специальный примитив для этого, который поидее и будет содержать ссылку на сам массив. Иначе как гц узнает связь между (ptr+i) и аллокацией c началом ptr?
Получается Vector это вью для последовательной аллокации? Тогда его всё равно нельзя использовать как параметр для ренжа с O(1) доступом, потому что за iota нету никакой аллокации. Зачем вы мне его предлагали?
Зачем для этого тайпкласс? Композицию функций уже отменили?
transform :: (Int -> a) -> (a -> b) -> (Int -> b)
Не вижу как сюда передать объект типа Range
, или какой вы там создали?
toList
В тот момент когда надо писать такие явные преобразования код перестаёт быть женерик.
А как вы там в типах отличаете ограниченные и неограниченные рейнджи?
По наличию перегрузки size()
auto x = zip_transform(std::plus{}, iota(3, 10), iota(3, 20));
static_assert(sized_range<decltype(x)>);
std::println("{}", x.size());
auto y = zip_transform(std::plus{}, iota(100), iota(200));
static_assert(!sized_range<decltype(y)>);
Потому что контейнер тегирован s, которая должна иметь возможность быть произвольной во всём скоупе runST (об этом говорит тип runST). Как только вы его куда-то кладёте, оно больше не может быть произвольным.
Т.е. у меня контейнер создаётся и умирает вместе с runST? Или создаётся мутабельный вью?
Я хочу взять например вектор, мутировать его в одном потоке, потом мувнуть его в другой поток и продолжить мутировать там.
А подёргать STM внутри runST тоже нельзя, потому что STM дёргается через atomically , которая живёт в IO, и которую нельзя поэтому дёргать внутри runST.
А как мне писать код который одновременно и читает что то из тред-сейф объекта расшареного и на основе этого модифицирует тред-локальный стейт?
Это ForeignPtr. GC знает, что и как финализировать.
Ок, получается имплементировать эту часть на языке не надо т.к. она встроена в язык. В джава или C# я такой штуки не знаю (но может есть).
Так за iota и рандомного доступа нет, поди пойми, что вам надо.
Для чисел есть
Зачем сюда Range? У нас тут отдельная ветвь дискуссии, где мы обсуждаем голые функции.
Вы почему-то продолжаете делать вид что не понимаете разницу между контейнерами, функциями и ренжами.
А если у меня там будет pair<width, height> size()?
Никто не запрещает в С++ делать именованные маркеры. А так да, подходы разные, и это уже другая тема. В расте с трейтами аналогичную проблему всё же как-то решают.
Ненене, вы саму zip_transform напишите. На плюсах же с шаблонами всё просто, так что как это будет выглядеть?
zip_transform
принимает сколько угодно ренжей, жду для начала вариадиков в хаскеле.
а оно не sized_range
Это недоработка реализации. Бесконечный размер на уровне типов приравнен к неизвестному размеру. Ваш пример с zip кстати как раз неизвестный размер тоже не покрыл.
Если игнорировать нюансы со ссылками то можно повторить ваш искуственный пример как-то так
template<typename F>
struct Unbounded {
F f;
};
template<typename F>
struct Bounded {
F f;
size_t len;
};
template<typename T>
concept HasLen = requires(T x) {
x.len;
};
auto zipWith(auto f, HasLen auto a, HasLen auto b) {
return Bounded([=](size_t i) { return f(a.f(i), b.f(i)); }, std::min(a.len, b.len));
}
auto zipWith(auto f, HasLen auto a, auto b) {
return Bounded([=](size_t i) { return f(a.f(i), b.f(i)); }, a.len);
}
auto zipWith(auto f, auto a, HasLen auto b) {
return Bounded([=](size_t i) { return f(a.f(i), b.f(i)); }, b.len);
}
auto zipWith(auto f, auto a, auto b) {
return Unbounded([=](size_t i) { return f(a.f(i), b.f(i)); });
}
Так делать не надо потому что это не реализует нужных концептов для итераторов/ренжей.
Хотя я пока писал понял что смысла в таком Unbounded нету совсем. У нас либо рандом аксесс и тогда мы знаем границы, либо не знаем границ и достаём по-одному. Получается бестолковый пример обсуждаем :/
Мутируете, делаете freeze, кладёте в TVar или что-то подобное, в другой транзакции достаёте, делаете thaw, мутируете.
сложность thaw O(n), т.е. оно делает копию. Вообще продолажая тему про IORef я не понял как там атомарно поменять что-то размером больше инта.
В сложных примерах я не замечу, что два раза читаю из IORef
Если есть гарантия что владение переменной однопоточное, то в чём проблема?
Если можете как человек гарантировать, что это безопасно,
Посыл нашего обсуждения что гарантировать должен компилятор, а не человек.
запилят поддержку линейных типов.
Поэтому раст тут получается впереди, о чём изначально и говорилось по ссылке. Борроу чекер в паре с send/sync даёт больше выразлительности.
ой, кстати, да, как реализовать рейндж всех нечётных интов одним iota, без фильтра?
можно cделать трансформ из n в 2*n + 1
а то вы так долго напирали, что у меня немощно и плохо всё
так вы повоторите хоть то что в iota есть. Если бесконечный ренж неподдерживается то ладно, я вроде такое не просил.
Только в очередной раз не сделали
их размер определяется наличием имени,
там проблема наверное в том что старые итераторы в уже существующем коде хотели продолжать поддерживать, в новом коде iterator_category определяется.
Хоспаде, ну наконец-то!
Я ничего сложного не просил. Просто есть вектор vec, r1 = vec | transform(f)
известен размер и есть доступ к элементам, r2 = vec | filter(g)
тут размера нету есть только forward итератор. Есть например
auto foo(range auto r) { return r | transform(h); }
Сохраняются свойства и при передаче r1 и r2, удобно.
На уровне реализации во всех этих рефах хранится указатель, поэтому атомарность бесплатная.
Если передаётся рефы всё равно вопрос как их менять. Либо копировать, либо менять инплейс.
list1 = foo 5 (Bound 10)
list2 = foo 5 Infinity
Во-первых неудобно, везде в других языках просто пишем iota(5, 10) или iota(5) не говоря уже про языки где есть именованные аргументы.
Во-вторых у вас на тайплевеле потерялась информация о том закрытый ренж или открытый
Ну вот на самом деле у вас и будет тысяча строк которые не работают без сахара в компиляторе и не реализуют половину фич из цпп.
И что мне в плюсах дальше с этой информацией делать?
Использовать. У вас в случае с енум на каждое использование ренжа будет теперь в рантайме бранч - есть/нет конечное значение.
В хаскеле весь [] это какая-то внутрення структура реализованая в компиляторе. Точнее часть более общей концепции ленивости. Тут опять сравнение не уместно.
Это не годится. У вас на каждую случай свое имя как в си. Так я не могу написать обобщенный алгоритм который принимает как конечный так и бесконечный интервал.
Опять контекст потеряли. я хочу
auto f(start, end) { return iota(start+1, end); }
за час пердолинга не смог
auto safeIota(auto&& start, auto&& end) {
if (start <= end) return rv::iota(FWD(start), FWD(end));
else return rv::iota(start, start);
}
делается за пол секунды
Не вижу никакого абьюза. Все работает как изначально и задумывалось
Как обычно условия меняются находу.
Если вы хотите вернуть разные ренжи в зависимости от рантайм значения, то задача требует динамического диспатча. Смысла в этом мало, если вы уже передаёте pred, то что мешает передать сразу консьюмера результирующего ренжа? В отличии от динамеческих языков в С++ нам не нужно стирать тип, информация о всём пайплайне трансформаций сохранятеся и может быть использована.
Вы про типы, что ли? Нет, я хочу тот же тип, просто пустой.
Так в типе хранится вся трансформация. Иота поверх которой трансформ, поверх еще что то, имеет другой тип чем когда изначально пустой. А стереть тип значит сказать что есть просто какой-то ренж, как получен - не знаю, ограничен сверху или нет - не знаю, и т п. Возможность сохранить эту информацию мощнее чем невозможность ее сохранить. Поэтому сделать обертку всегда можно.
Не очень понял, вы хотите просто по рантайм условию обрубить ренж?
псевдо код wantEmpty ? (r | take(0)) : r
?
У нас есть rng(args...)
, можно ли это вообще вызывать если pred(args...) == false
? А потом, ну получили мы тип не факт что он default constructible и его дефолт это пустой ренж. Может там захватывается ссылка на какой то контейнер.
Так почему нельзя сделать рейндж того же типа, который выдаётся переданным генератором, но пустой
Зачем для пустого рейнджа что-то куда-то захватывать?
если мы говорим о типе с точки зрения С++, то нельзя потому что в типе хранится весь пайплайн преобразования. Для пустого ренжа он пустой, для другого другой (в том числе с захватами). Поэтому надо привести к обшему типу с динамическим диспатчем. Или как вы любите упростить задачу и вернуть optional<rng>
.
Всё что можно сказать исходя из факта наличия таких строк в плюсах - это что плюсы совместимы с С, и предлагает помимо этих строк свои строки. Почему совместимость с С занесена в минусы опять же непонятно
Будет звучать для особо чувствительных жестоко, но используйте C.
"Рассмотрим пример - dynamic_cast<> и RTTI (runtime type information). Это отключаемая фича в С++, но по-дефолту она включена и многими используется. Многими программистами С++ она воспринимается как бесплатная" - это какими многими? Джунами? Мб. Почему отсутствуе такого базового знания приписывается в минус языку решительно непонятно. На счёт отсутствия методов для работы со строками приводится в пример как раз то что есть split и starts_with. Это специально так? В целом много чего по делу. Но так же много такого как на примере выше.
Очередная статья от человека, который не разобрался в современных возможностях языка, но который смело заявляет:
Честно говоря, я даже не могу вспомнить что там в последних версиях было. Просто какая-то тарабарщина, малоприменимая в моей реальности
Моя самая "любимая" древность в языке - это инклюды. Честно говоря это похоже на какое-то издевательство. Я понимаю как к этой идее пришли несколько десятков лет назад. Но почему это существует до сих пор?
Уже в C++20 появились модули, решающие многие проблемы инклудов. Да, поддержка не везде и неполная, но в GCC уже большая часть предложений реализована.
Лично для меня еще существенным минусом является вынесение общих функций наружу, вместо использования их как функций‑членов класса. Мне, например, гораздо проще и очевиднее вызвать
myVector.find_if()
через точку, чем вызывать ее как внешнюю функцию с передачей итераторов внутрь:
А если захотим написать свой stl-compliant контейнер и реализовать для него find_if? Писать с нуля?
необходимость передачи
.begin()/.end()
в большинстве случаев. Это удлиняет и засоряет синтаксис
См. std::ranges.
Далее, что касается SFINAE: значительная часть задач, решаемая этим, кхм, механизмом теперь решается концептами. Да, не полностью, но прогресс большой. Это же позволило улучшить и сообщения об ошибках.
Это отключаемая фича в С++, но по-дефолту она включена и многими используется. Многими программистами С++ она воспринимается как бесплатная, однако при включении RTTI постоянно происходит скрытая работа в рантайме.
Так это проблема программистов, а не языка получается? Отключаем RTTI и довольствуемся zero overhead.
А если захотим написать свой stl-compliant контейнер и реализовать для него find_if? Писать с нуля?
нет. Мне кажется было бы неплохо чтобы были оба варианта - внешняя и внутренняя функции. Для своей реализации контейнера легко добавить внутреннюю функцию, использующую внешнюю: auto find_if(xxx) { return std::find_if(begin(), end(), xxx); }
Но сколько раз за все время вы писали свой контейнер? А сколько раз вы набрали begin(), end()
? Вот буквально предлагаю провести эксперимент и найти все вхождения этой подстроки в вашем текущем рабочем проекте
>Для своей реализации контейнера легко добавить внутреннюю функцию, использующую внешнюю: auto find_if(xxx) { return std::find_if(begin(), end(), xxx); }
А также rfind_if (+ конст версия) и find_if (конст версия) + итератор-адаптер туда не прокинуть
> Но сколько раз за все время вы писали свой контейнер?
Итераторы - это не только про контейнеры.
Кажется вы просто не оценили гибкость итераторов
> А сколько раз вы набрали begin(), end()
Выше уже написали, что сейчас есть range'и , если надоело печатать
Кажется вы просто не оценили гибкость итераторов
Именно. Встроенные функции хороши только когда надо что-то сделать со всем контейнером. Не спорю, это составляет львиную долю использований. Но! Что если надо искать не с 0-го элемента, а с 1-го или не до последнего, а до предпосленего. И т.д. и т.п. Так что делать тупую встроенную функцию в которую не надо передавать begin()/end() - это по сути шаг назад.
Зачем вы упираетесь в ограничение что либо так, либо сяк? Я про удобство кодирования, от лишней функции внутри контейнера ничего страшного не случится. А все внешние функции пусть остаются внешними и гибкими. Речь же про 99% случаев когда тебе нужно передавать begin()/end() - а это тупая работа
напиши 1 раз шаблонную функцию которая за тебя это сделает и проблема решена не вижу тут трагедии. Случится, её нужно поддерживать и собственно зачем это делать для алгоритмов ни как не привязанных к структуре хранения данных? предположим у нас 10 контейнеров и 10 функций и ты предлагаешь вместо того чтоб написать и поддерживать 10 функций добавить ещё 100 чтоб они просто были так как кому то лень писать begin/end, идея ВО, предложи на следующем собрании комитета стандартизации, хочется посмотреть как они долго ржать будут.
Изначально же разговор шёл про методы-расширения.
Условно говоря мы один раз пишем метод find, который можно вызвать как метод класса у любого объекта, который реализует begin() и end().
Если дальше проводить аналогию с методами расширения, плюсами и вашим кейсом, то тогда бы вызов find на вашем примере был бы что-то типа my_container.range(1..^2).find(...)
Где ^2 - это 2 элемента от end()
Хорошо, а если мы хотим не по всему контейнеру искать? У меня такая задача часто возникает. Можно конечно дублировать функцию, но зачем, если вариант с методом класса реализует частный случай и не обобщаем (без потери удобства)? Реализовывать весь набор основных алгоритмов для каждого контейнера - это куча бойлерплейт кода.
весь этот бойлерплейт будет написан в стандартной либе. Можно все завернуть в mixin, тогда вся эта портянка будет описана один раз. В своих коллекциях можно будет так же юзать стандартный mixin
Модифицированный вариант: https://gcc.godbolt.org/z/j5WsEo87E
Раскомментируйте последнюю строку в функции abcx()
и заставьте работать ваш вариант так же, как работает вариант с функцией abcy()
.
Думаю, вам станет понятно, почему был сделан выбор в пользу свободных функций.
И обратите внимание, как пришлось протаскивать конструкторы через два шаблонных класса, чтобы my_vector
можно было проинициализировать так же легко, как и std::vector
.
И обратите внимание, как пришлось протаскивать конструкторы через два шаблонных класса, чтобы
my_vector
можно было проинициализировать так же легко, как иstd::vector
.
my_vector
здесь чисто ради примера, потому что std::vector модифицировать я не могу.
Раскомментируйте последнюю строку в функции
abcx()
и заставьте работать ваш вариант так же, как работает вариант с функциейabcy()
.
я согласен, чуток универсальности теряется. Но это довольно синтетический кейс, по крайней мере для меня. Мне гораздо чаще нужна работа с конкретной коллекцией. А таких шаблонных функций с алгоритмами внутри мало, над ними можно попариться
я согласен, чуток универсальности теряется. Но это довольно синтетический кейс, по крайней мере для меня. Мне гораздо чаще нужна работа с конкретной коллекцией.
Видите, вы хотели бы, чтобы C++ был "заточен" под вас.
Но я почему-то думаю, что тот факт, что он "заточен" не под вас, — не повод для "Оды хейта C++". Видимо, не ваш это язык, не для вас, вот и всё.
Автор, прошу Вас сначала ознакомиться с синтаксисом русского языка. После "бунда" читать перестал.
Не знаю, писал ли кто то здесь про это, но если в статье это не исправлено, значит нет:
<<< Однако код"abrakadabra" == "abrakadabra"вернет false, ведь это два разных массива >>>
тут явно ерунда написана, так как в С++ будет возвращено значение true, можете проверить это на коде
printf("%d\n", "abrakadabra" == "abrakadabra");
Выше писали. Но также писали, что это совершенно не обязательное поведение и зависит от компилятора
Стандарт явно говорит, что это не обязательно. [lex.string]/9 (16, 15) в последнем стандарте (в 17, 20 соответственно).
приведите хотя бы один компилятор, который так не делает и будет выдано false? в статье не сказано, что может быть выдано или true, или false, утверждается, что только false, при этом если взять и скомпилировать самыми распространенными компиляторами, то получим совершенно иную картину.
Практически любой компилятор в режиме "отладка".
Вот, например, msvc: https://godbolt.org/z/5oT9xoafv
А кто мешает сравнивать строки? "abrakadabra"s == "abrakadabra"s
При этом, уверен, некоторые type trait классы все-таки реализованы на уровне компилятора.
Нет, в отличие от других языков, как, например, rust, в c++ практически нет никаких магических типов, трейтов и прочих вещей, которые делают то, что ты не можешь сделать сам. Единственное исключение, которое приходит на ум - это range based for loop, который требует наличия begin()/end() у объекта - на мой вкус весьма уродливо вкорячили это в язык.
Именно поэтому весь ваш батхерт на тему строк и умных указателей - это не фича языка, это фича библиотеки, которую можно выкинуть и взять свою.
Именно поэтому, ИМХО, язык такой многословный, как вы пишете.
Единственное исключение, которое приходит на ум
Вроде бы std::launder требовал поддержки на уровне компилятора.
И, возможно, std::start_lifetime_as, т.к. это именно что подсказка компилятору.
start_lifetime зависит от компилятора только транитивно из-за потребности в launder внутри, а так возможен. Вот offsetof банальный нельзя.
Я не совсем это имел ввиду. std::launder с точки зрения синтаксиса ничем необычным не является. Шаблон с одним параметров функции с одним аргументом и всё. Это не часть грамматики, это часть библиотеки. Пусть она требует некоторой поддержки со стороны компилятора, но это как раз нормальный способ абстрагироваться от implementation defined. Я же имею ввиду кейсы в другую сторону, вот как у range based for loop - с одной стороны методы begin()/end() - это обычные методы, с другой стороны от их наличия или отсутствия ломается синтаксис for(auto& x : some_collection). То есть грамматика языка (а for в данном случае это грамматическая конструкция) связана с неграмматической его частью - нельзя "настроить" range based for loop таким образом, чтобы он требовал не begin()/end(), а, например, start()/finish() - связь эта жестко зафиксирована. Таких мест в C++ чрезвычайно мало.
Понятно.
Тогда еще и structured binding для пользовательских типов можно вспомнить.
Приведите пример магического трейта в rust, пожалуйста. Такого, что вам нужен свой аналог, но язык не позволяет это сделать.
Ну, например, трейт Try. С точки зрения синтаксиса - это ничем не отличающийся от других трейтов трейт. Но именно он отвечает за возможность пользоваться оператором ?. То есть это magic trait. В Яве, например, чтобы бросить Exception необходимо наследоваться от класса Exception - хотя вроде бы это класс, как класс, но выкинуть его и написать свой нельзя. Это magic class. В паскале println - вроде бы обычная функция, а на самом деле нет, так как calling convension не позволяет делать функции с переменным количеством аргументов, и написать свою такую функцию нельзя. Это magic function.
А в С++ есть исключения, и магические операторы try/catch, ваша мысль понятна.
Но базовые знания по Rust у вас отсутствуют. Никакого трейта Try не существует!
Оператор ? это синтаксический сахар для обработки ошибок с типом Result.
Строка let res = func()?;
разворачивается компилятором в let res = match func() {
Ok(res) => res,
Err(err) => return err.into(),
};
То же самое вы можете написать без оператора ?. Хотя обычно никто не против синтаксического сахара в любом языке.
Но я так и не понял, зачем вам может понадобиться свой аналог оператора ?.
А в С++ есть исключения, и магические операторы try/catch, ваша мысль понятна.
try/catch являются не перегружаемыми операторами. А кидать в качестве исключения можно хоть int.
Но базовые знания по Rust у вас отсутствуют.
Оу! Кажется, я понял, что имел ввиду один из комментаторов в недавнем посте, про токсичность растеров.
Никакого трейта Try не существует!
https://doc.rust-lang.org/std/ops/trait.Try.html
"Давай, до свидания" (с)
Err(err) => return err.into(),
И тут внезапно натыкаемся на код, где функция, внутри которой дёргается ? возвращает Option.
Но я так и не понял, зачем вам может понадобиться свой аналог оператора ?.
Написать свой Result с блекджеком и дамами с низкой социальной ответственностью, в котором enum будет не с вариантами Ok и Err, например.
https://doc.rust-lang.org/std/ops/trait.Try.html
"Давай, до свидания" (с)
Это эксперимент из nightly компилятора, и не известно когда будет к стабильном, и будет ли вообще. Ближайшая аналогия - proposal из С++, который специальным ключем поддерживается в gcc.
И тут внезапно натыкаемся на код, где функция, внутри которой дёргается ? возвращает Option.
Для Option синтаксический сахар раскроется по-другому. Это проблема?
Оу! Кажется, я понял, что имел ввиду один из комментаторов в недавнем посте, про токсичность растеров.
То что вы написали никогда не было в стабильном компиляторе. Что я написал про оператор ? - действует как минимум 9 лет с выходя Rust 1.0. Кроме того это самая базовая часть языка, без которой не получится написать буквально ни одной строки кода.
Но вы почему-то считаете себя достаточно опытным чтобы писать недостоверную информацию про Rust.
Написать свой Result с блекджеком и дамами с низкой социальной
ответственностью, в котором enum будет не с вариантами Ok и Err,
например.
Ну ок. Но практическое применение этого какое? Всё-таки очень мало какие языки позволяют добавлять кастомные операторы, или изменять уже существующие.
Но вы почему-то считаете себя достаточно опытным чтобы писать недостоверную информацию про Rust.
То, что трейт есть только в nightly builds, не говорит о том, что информация, о которой я пишу, недостоверна. Как и то, что на практике нет греха в применении proposal в C++. Так получилось, что просто я читал про этот трейт в тот момент, когда завязалась эта дискуссия, и я его привёл. Мог бы привести трейт Add, но мне показалось это слишком банальным.
А вот то, что вы сходу переходите на личности, и делает вас токсичным фриком, с которым общаться захочется только такому же. Так что хорошего дня и добра.
То, что трейт есть только в nightly builds, не говорит о том, что информация, о которой я пишу, недостоверна.
Это внутренняя реализация компилятора. Сегодня он есть в nightly, а прямо завтра может не быть. И такое реально много раз происходило с типами, которые есть только nightly, потому-что это эксперименты.
Кроме того вы не понимаете, что такое nightly компилятор. В нем нормальный рабочий код может не скомпилироваться, а может сгенерить неправильный ассемблер. А завтра сгенерит правильный. А при полной перекомпиляции снова неправильный.
Скажите, вы в продакшен на с++ тоже генерите код альфа-версией gcc?
"Давай, до свидания" (с)
Переход на личности первым сделали вы, но токсичный почему-то я. Просто прекрасно!
Кстати, а в чем магичность трейта Add?
В языке есть реально 2 магических трейта, но это самый обычный.
try/catch являются не перегружаемыми операторами. А кидать в качестве исключения можно хоть int.
А какие операторы являются перегружаемыми?while
, for
, может быть, if
?
Операторы арифметических и логических действий, оператор индексации, вызова, взятия адреса, разыменования, приведения типов и ещё несколько.
Операторы, которые влияют на flow кода, перегружать нельзя.
Операторы арифметических и логических действий, оператор индексации, вызова, взятия адреса, разыменования, приведения типов и ещё несколько.
Это — не операторы.
Операторы, которые влияют на flow кода, перегружать нельзя.
Никакие нельзя, нет такого понятия даже.
странно, но cpp referenced говорит что оператор https://en.cppreference.com/w/cpp/language/operators
странно, но cpp referenced говорит что оператор
Сложности перевода.
Почему-то никто не говорит о стейтментах.
https://en.cppreference.com/w/cpp/language/statements
Английское слово operator переводится русским словом операция.
это в каком словаре?
Основное значение "оператор" и operator в русском и английском совпадают. Значит "operator" переводится в "оператор".
Если хотите, направьте ваши замечания в Институт русского языка.
Основное значение английского слова "operator" в контексте программирования – это "операция". А основное значение русского слова "оператор" в контексте программирования – это "statement".
"Operator" переводится как "оператор" только когда речь идёт о человеке или о математической функции, которая опять-таки по сути является операцией.
Спасибо за эту путаницу можно сказать советским программистам от математики, которые когда-то перевели слово "statement" как "оператор", хотя более разумные люди предлагали буквальный перевод "утверждение".
a = a + b;
где здесь оператор?
Функция с именем main - обычная функция? Тернарная условная операция - обычная операция? int - обычный тип? & и * - обычные операции? Lambda - обычное значение?
функция с именем main - обычная функция. Подменив лодер стандартного runtime на свой, можете использовать функцию vadimr().
Это где в описании языка написано?
Ещё раз, для тех, кто в танке. Функция main не является ключевым словом. Не является частью грамматики языка. Это особая функция с точки зрения runtime. Это не особая функция с точки зрения грамматики. Это не особая функция с точки зрения её имплементации. В сущности, вы её даже можете вызывать сами - хотя это, конечно, довольно странно, но принципиально возможно.
Я же пишут о том, что во многих языках (rust я только в качестве примера привел, в других каментах есть и java, и pascal). Есть особенные сущности более высокого уровня, которые имеют специальный смысл.
Использование main, тернарного оператора, типа int, оператора & (кстати, этот перегружается) и *, а также лямбд не приводит к тому, что вам нужно создавать какую-то вторичную семантику. Условно говоря, если я перегрузил оператор +, то я могу вызвать эту перегрузку только используя оператор +. В расте же я могу использовать +, а могу тогда вызывать метод add, который получается неразрывно связан с оператором +.
В плюсах тоже такая тема начала по-тихоньку появляться начиная с 11-го стандарта в разных местах. Это немножко портит математическую чистоту языка.
Что за вторичная семантика такая?
Ну вот вы можете создать у класса метод abcd() - вокруг него не будет завязана какая-то иная функциональность, кроме собственно возможности вызывать этот метод. А вот, например, как я уже упоминал выше, методы begin()//end() помимо того, что их можно вызывать, участвуют в обеспечении работы синтаксиса range based for. Выглядит это криво. Как если бы метод new() у класса служил бы, скажем, конструктором. Но для конструкторов сделан специальный синтаксис - по нему сразу видно, что речь не об обычном методе класса, а об особенном. А по begin()/end() не видно, что за ними стоит какая-то дополнительная семантика. Но в плюсах такие костыли встречаются редко. В rust же на это натыкаешь почти сразу.
В плюсах я могу написать
a + b
, а могу написатьa.operator+(b)
.
Это всего лишь альтернативный синтаксис вызова оператора +. В расте же операторы замешаны с методами.
В плюсах я могу написать
a < b
, а могу написать заклинание сstd::less
.
Не надо сюда примешивать std::less! std::less это не более чем шаблон, который реализован грамматикой языка, и неспециализированная версия которого просто вызывает всё то же a < b
,
Я попытаюсь сформулировать следующим образом. Если из плюсов выкинуть стандартную библиотеку, то ну, скажем, 95% функциональности языка останется. И в рамках этой функциональности вы можете написать полностью свою обвязку, и использовать в ней совершенно любые идентификаторы, не предопределенные каким-то святым писанием. А если научится пользоваться builin'ами, то и вообще все 100%. С растом, похоже, что так нельзя - вам придется описывать свою обвязку по весьма определенным лекалам. Если вы любите аналогии, то выглядит это так, что если бы для машин на бензине было бы жестко определено, что руль должен быть слева, мотор спереди и привод только передний.
Тернарная условная операция - обычная операция?
Обычная не перегружаемая операция. Но там где операция перегружаема, например, operator + вы перегружаете именно operator +, а не имплементите trait Add с методом add().
А почему она не все свои аргументы вычисляет?
Или вот && и ||.
Кстати, как мне сделать свой собственный оператор? Хочу
<$>
, чтобы было удобнее
А что, в расте можно создавать собственные операторы? Или вы про другие языки? Я не думаю, что это хорошая идея в принципе, так как это сильно усложняет как минимум ту часть парсера кода, которая нарезает на токены. Что-то смутно вспоминаю, что подобная возможность была в Haskell'е, но с точки зрения читабельности кода - это примерно на уровне perl =) Сразу говорю, что IMHO, и вопрос вкуса фломастеров =)
Забавно, что добрая половина ненависти к тем вещам, которые лично мне кажутся преимуществами языка.
А в целом напоминает
- Не люблю я кошек!
- Да вы просто не умеете их готовить!
После бреда про строки можно дальше не читать. На лицо skill issues. Можно было хотя бы проверить на таком вот элементарном примере свои высказывания. Да и складывать заведомо известные фиксированные строки, после чего жаловаться на них - поведение надмозга.
if ("abrakadabra" == "abrakadabra")
Это работает чисто из-за оптимизации компилятора. Факт остается фактом - это два поинтера. Сравнивать два поинтера, надеясь что там одинаковые данные - не очень хорошо
Сложения строк у вас нет, поставьте между ними плюс. Компилятор считает это одной строкой
На лицо skill issues )))
Сложение строковых литералов, о которых идет речь, подразумевает попытку их конкатенации, которая описывается для этих сущностей стандартным поведением компилятора и стандартом ISO, как то, что продемонстрировано в моем примере. Соответственно, не существует ни одной причины ставить знак сложения между ними. Если хочется получить другое поведение, всегда можно перегрузить оператор. Аналогично и со сравнением этих литералов при включении хотя бы -O1, который гарантирует отсутствие дополнительного выделения памяти, но опять же, нет ни одного реалистичного примера для сравнения двух заранее известных литералов так, как это описывается в статье. И в конце концов, существует тысяча и одна стандартная функция, позволяющие выписывать сколь угодно пируэтов, как безопасных, так и не очень. И если придирка конкретно к работе с чистыми Си строками, то надо подбирать соответствующий пример, отражающий претензию конкретно к ним, а не к строковому литералу.
Пара фич, из самого очевидного, что я хотел бы видеть в С++:
...
extension methods. Это возможность вне класса добавить какие-то методы API к нему...
Классы-перехватчики (interceptor classes)
Никто в здравом уме не будет делать две разные сущности с одинаковым именем в одном пространстве имен, но в разных сорцах/библиотеках.
Вот на этом невысказанном предположении и держится весь хрупкий механизм нашего молодого народовластия…
Ода хейта C++