На существующие проекты можно натравить нейронку, да. Но если проект пишется новый, а пишут его сразу эксперты? Тогда ЛЛМ не сказать, что имеет много смысла в этом.
Тем не менее в 2026 году написание кода на С или С++ без анализа на UB с помощью LLM уже следует расценивать как нарушение SOX, да и просто как банальную безответственность. Если уж разработчики OpenBSD более 30 лет не могут отыскать эти проблемы, то каковы шансы у всех остальных?
Такой подход может не масштабироваться на крупные кодовые базы, но конкретно в своих проектах я прошу LLM найти UB, а при необходимости объяснить и исправить. После этого я внимательно изучаю результат, пока не убеждаюсь, что баг реально присутствовал и был исправлен.
Это перевод, но всё же. Почему автор вообще считает, что LLM могут искать UB лучше людей вообще всех. Да, LLM оказались лучше конкретно этого человека, но это ведь не говорит о всех людях. На моей практике работы с LLM они пропускали UB (в C++ только), которые мне были видны сразу.
Если "эксперт" не может найти UB в примерах этой статьи, то эксперт ли это?
Современные плюсы действительно достаточно приятны. Вот те же концепты, рейнджи, constexpr, модули, deducting this, корутины (которые, впрочем, не самые удобные), фолды, #embed и прочее.
type* ptr = new type;
type* ptr2 = new type;
delete ptr2;
delete ptr;
Вполне может вылететь исключение из второго вызова new. Тогда получится так, что delete для ptr никогда не вызовется. Вот и утечка нарисовалась. Разумеется, так вылететь может не только внутри new, но и много где ещё (хотя оно и не вылетит при использование "Си с классами"). Хотя если рассматривать сценарий, где нехватка памяти это сразу полное завершение программы и там и там, то разницы особой не будет.
Раст сам по себе не лучший язык. Даже шаблонов из C++ нет. Вся работа с кодогенерацией работает через его макросы, которые работают с текстом, а не с типами. Всё ли в расте с аналогом constexpr хорошо? https://godbolt.org/z/sn3cbPYKz и даже не заифать https://godbolt.org/z/TGaGPP37c
В расте нет и исключений (они как-бы есть в виде паник, но кто их применяет везде?).
Перегрузок в расте нет.
Рефлексии в расте тоже нет, когда в плюсах она есть.
Ни разу не пробовал, так что не знаю. А делать так для оптимизации кода. Возможно, стандарт должен разрешить дополнительно оптимизировать код для объектов без тега. Примерно как с volatile, если переменная помечена таким тегом, тогда все чтения и записи делаем скурпулёзно, а если нет, то оптимизируем.
Стандарт и так разрешает оптимизировать код. Компилятор имеет право вырезать лишние конструкторы, деструкторы и прочее. Только есть разница в том, что считается наблюдаемыми эффектами. Если аллокации такими не считать, то компилятор будет иметь право работать и с длинными std::string (когда не срабатывает SSO).
После чего пользователи начинают жаловаться, что у них dll не хватает. Или, допустим, запускаешь свою прогу, а она подхватывает dll от notepad++ и делает что-то не то.
Первый раз слышу про такие проблемы. Но у меня и с виндой опыта разработки нет, 100% кода пишу на лине.
Уже где-то писал, чтобы другие языки тоже могли использовать эти библиотеки. А если речь про конкретно библиотеку для метапрограммирования, то понятно, что ей C апи не нужен.
Мне как-то приходилось компилятор править, так что при некотором желании можно, но сейчас наверное нет смысла. В компиляторе же есть упрощение выражений, состоящих из констант, типа (4+5)*6. Некоторая интерпретация кода вроде тоже есть. Тут конечно вопрос, как добраться до кода функций, остаётся полагаться на то, что они в заголовочном файле. std::string хоть и не pod, но в ней какие-то инты и указатель на массив pod’ов, можно и это попробовать проинтерпретировать.
Как Вы это в стандарт описывать собираетесь? И зачем так делать?
Какое отношение динамические библиотеки имеют к вопросу о стандартных библиотеках? Даже не знаю. Расслабились все, место на диске не жалеют. Скачал clang, а там пачка почти одинаковых exe’шников по 100 с лишним мегабайт весом.
Так дело в том, что мне вообще нет дело до dll-ек. Я подключаю библиотеку в CMake, а дальше уже само находит и подключает. До уровня .dll опускаться смысла нет.
Что-то узкоспециализированное. nix вообще с трудом гуглится. Если речь про язык программирования, то говорить, что в C++ что-то есть, ссылаясь на другой язык, ну не знаю.
Nix это не только язык, но ещё и ОС и пакетный менеджер. nixos.org.
Потому и написал “может”, что это очевидно на стороне пользователя, но не компилятора. Можно предположить такое: если в выражении участвуют POD-типы, то можно посчитать результат и вписать в память. Если используются указатели на POD, то можно так же посчитать результат, выделить память и заполнить.
А можно увидеть какое-то более подробное описание алгоритма? И какое отношение имеет std::string к POD типам?
О том и речь, чтобы не приписывать.
Суть в чём? Чтобы функция могла считаться constexpr по умолчанию? Но подозреваю, что могут возникнуть некоторые проблемы.
Забиваем в поисковик “как прочитать json в python” и видим предложение использовать стандартный модуль json. Потом там же пробуем “как прочитать json в C++”, и там не видно boost. А ещё могу по второму разу спросить, как в boost называется dll, которая читает json? И да, он тяжёлый, и это проблема. В питоне хоть на маке, хоть на винде импортируешь нужную либу через pip и в продакшен. То есть вопрос не в возможности как таковой, а в пользовательском опыте. C++ просто неудобно пользоваться.
Зачем мне знать про какие-то там dll-ки? Какое отношения они вообще имеют к вопросу? Опять же, существуют CPM.cmake и nix, с которыми подключить библиотеку дело достаточно быстрое и удобное.
Предлагаю ничего не делать. Нам же не обязательно что-то сделать со всеми библиотеками в мире, достаточно добавить в SAL самые часто используемые вещи. Ну и собственно, шаблоны и шаблоны, вроде как никто не говорил, что в SAL не должно быть инклюдов. С-шники не будут плакать, если не смогут воспользоваться именно этим модулем, кому надо, тот и импортирует. Да и в принципе никто не мешает сделать отдельно папку с C-инклюдами, отдельно с C++. Вот там можно обёртки из классов написать и исключения кидать.
Предлагаете тогда не закидывать туда огромную кучу нормальных библиотек, а оставить только библиотеки без шаблонов? К примеру, Boost.Asio много использует шаблоны достаточно много, range-v3, ctre и т.д.
Зачем вообще он нужен будет тогда, когда там нет огромного количества полезных и используемых библиотек?
Зачем вообще ограничиваться только теми, у которых есть и C-апи? Плюсы это ведь не "Си с классами", а сильно более продвинутый язык.
Это лучше прозрачностью написанного. Насчёт порядка выполнения блоков - тут да, надо подумать. Может обязать пользователя вручную это гарантировать, вызывая функции инициализации вручную.
Не наблюдаю тут никакой прозрачности. Можете рассказать про неё подробнее?
наверное не стоит давать пользователям выходить за границы блока.
На что ниже у меня там тоже есть свой комментарий.
Но что будет если пойти по обратному пути уменьшения возможностей? Получится такой мерзотный язык как Go. Язык должен быть удобным инструментом для использования, а не удобным для освоения. В том же Go пошли обратным путем и сделали язык очень простым, но и максимально неудобным. Те же генерики ввели вот только недавно, а ведь такие средства для создания общего кода максимально полезная вещь.
Как по мне, в плюсах возможностей всё ещё недостаточно, а с каждым стандартом язык становится лучше. Но и возможности ради возможностей тоже не будут хороши. Они должны хорошо вписываться в язык, не должны приводить к бессмысленному росту языка, но должны приводить к его развитию. Лучше добавлять общие и гибкие решения, а всё остальное наслаивать уже на них, а не добавлять "адхоки" на каждый случай.
#include <array>
void foo() {
constexpr auto members = std::array{^^int, ^^char, ^^float};
template for(constexpr auto t : members) {
typename [: t :] x = 0;
}
}
Может даже концепцию “значения” стоит распространить на вычисление выражений, string("asd") + string("fgh") - здесь ни к чему дёргать конструкторы с деструкторами. А ещё, возможно, так constexpr сможет получаться автоматически.
Можете тогда предложить свой вариант того, как это могло бы выглядеть? Нужно ведь понимать, что std::string достаточно честный тип, который находится в стандартной библиотеке. Как это совместить со всем остальным в языке? К примеру, с тем же std::vector, std::variant, placement new и ручным управлением времени жизни объекта. constexpr и так получается практически автоматически, достаточно лишь constexpr приписать ко всему (да и if consteval блоки там, где компилятор не может вычислить куски в constexpr, но это более редкий случай).
Это чуть ли не самое очевидное изменение. Почему у питона есть стандартные прикладные библиотеки типа json, xml и т.д., а когда хочешь прочитать json на C++, надо бегать искать нормальную библиотеку и пытаться подключить.
Boost уже стал второй стандартной библиотекой. Если не важно то, что сам Boost это достаточно тяжелая зависимость, то можно брать его, хотя и не всё там сделано в самом удобном виде, да и не всё есть.
Ну и конечно, сами библиотеки должны быть сишными (с нормальными именами функций то есть), глядишь, и другие языки на них пересядут. Пишешь pic install requests и получаешь библиотеку requests с помощью Pack Installer C - удобно же?
Про имена функций непонятно (буду дальше описывать что если бы Вы предлагали обязать их работать и через C апи в том числе). А что Вы предлагаете делать с библиотеками вроде Boost.Hana, которые целиком на шаблонах сделаны? С таким подходом нельзя будет делать фактически ничего, что делает C++ самим собой. C++ без исключений, шаблонов и constexpr уже сложно назвать C++. С пакетными менеджерами уже давно есть CPM.cmake, да и можно использовать пакетные менеджеры ОС. У некоторых из них возможностей вполне достаточно. К примеру, nix.
Код, запускаемый на этапе компиляции
Так и не ясно, чем это было бы лучше подхода с constexpr сейчас. С деталями тоже неясно. Так и в предоставленном варианте нет возможности в этом блоке переиспользовать другие элементы, которые были вычислены в предыдущих. Оно всё равно потребует в данных фиксации того, что они вычислены на этапе компиляции, ведь компилятору это определить нетривиально, т.к. проблема останова и прочее.
Контейнеры с типами, чтобы не мучать компилятор шаблонами с переменным числом аргументов
Уже есть в виде std::meta::info и любых контейнеров, которые умеют хранить значения (к примеру, std::vector). Смотреть рефлексию для C++26.
Кодогенерация
Реализовывать Вы это как собрались? Предположим, что в таком блоке для "выражений" будет добавлено что-то, что закроет функцию и начнёт новую. Тогда реализация крайне нетривиально. Но есть и более важная проблема. В C++ шаблонах компилятор может заранее парсить их тело, ещё до их инстанцирования. Если там будут произвольные варажения, то это сломает эту модель и до инстанцирования о шаблонах нельзя будет сказать практически ничего.
Хорошо, предположим, что они такие блоки могут возвращать только специальные куски AST. Тогда эта модель с некоторыми изменениями кое-как, но работать может, хотя и сильно сузит возможности для кодогенерации. Но возникает один вопрос зачем это всё? Намного более гибким будет подход с возможностью задать новую функцию/тип/прочее через расширение к compile time рефлексии в C++26 с нужным именем, а после уже определить там тело через типы плюсового AST в стандартной библиотеке (можно и немного сахара для такого придумать, но необязательно).
Можно было бы изучить вопрос получше, а не сразу выкидывать про это на Хабр? Или Вы воспринимаете Хабр как место получения критики идей и их развития? Если так, то это имеет смысл. Но в итоге это должно не на Хабре дальше быть с таким, а развивать до зрелости подхода и пойти в пропозал для стандарта.
В C++ есть std::ranges::views, которые позволяют лениво обрабатывать это всё. В C++ в компиляторах есть оптимизации хвостовой рекурсии, пусть и не гарантированные. В стандартной библиотеке есть std::expected и std::optional.
Есть и вот такое:
template <template <typename...> typename>
struct a {};
template <typename...>
struct b {};
using c = b<a>;
Типы все неизменяемы, а метапрограммирование с типами фактически обязано быть в ФП.
Есть нормальный const.
Хотя исключения из плюсов особо не вырезать (хотя некоторые проекты и работают без исключений, это не полный язык). Но такая ли проблемы исключения? Они позволяют нормально композировать функции, тогда как с ошибками через монады бы так не вышло. Взять те же std::ranges как пример библиотеки. Если использовать std::expected, то эта библиотека фактически обязана была бы везде брать std::expected, а после автоматически их соединять. Таким образом, на границе интерфейса библиотек всегда нужно было бы прописывать std::expected апи, везде прописывать не самый удобный апи для работы с ним.
К тому же, исключения можно сделать и через алгебраические эффекты, но не в C++.
Про синтаксическую поддержку монад. Из коробки есть разве что корутины, которые позволяют выразить некоторые монады вроде Either или Maybe, но возможности всё равно достаточно ограничены. Но есть и сторонние библиотеки для создания синтаксической поддержки. У меня про неё написаны 2 статьи.
Паттерн матчинга и правда нет. Но есть std::variant с паттерном overloaded. Что позволяет использовать перегрузки (родное средство языка, между прочем) для "матчинга" по std::variant.
Для такого случая правила overload resolution с концептами тривиальны. С концептами там меньше повторений кода будет.
Больше символов? else if constexpr(std::is_same_v<T, V1>) {} против ,[&](V1& v){}. И это в случае явного перечисления через перегрузки. Если с концептами, то там сильно меньше получается.
Вариант оригинальный (про него же речь?) то справляется. Но элегантным его точно не назовёшь. Про проблемы варианта через енам сказано.
Но тут нужно руками строить стейт машину внутри функции read_next. Синтаксис с LET _ IS(...) позволяет этого избежать.
К примеру, в репозитории есть следующий код:
Где YIELD реализован через LET IS.
Подробнее про это можно прочитать тут.
На существующие проекты можно натравить нейронку, да. Но если проект пишется новый, а пишут его сразу эксперты? Тогда ЛЛМ не сказать, что имеет много смысла в этом.
Для начала бы надо найти компилятор любого C++ вообще, ведь ни один не соответствует стандарту точно.
Да нет, сам C то совсем платформонезависимый. Просто не полагаться надо на детали конкретных платформ, а писать по стандарту.
Это перевод, но всё же. Почему автор вообще считает, что LLM могут искать UB лучше людей вообще всех. Да, LLM оказались лучше конкретно этого человека, но это ведь не говорит о всех людях. На моей практике работы с LLM они пропускали UB (в C++ только), которые мне были видны сразу.
Если "эксперт" не может найти UB в примерах этой статьи, то эксперт ли это?
Вы бы хоть деталей каких дали. Как по мне, дизайн у std::variant отличный.
Современные плюсы действительно достаточно приятны. Вот те же концепты, рейнджи, constexpr, модули, deducting this, корутины (которые, впрочем, не самые удобные), фолды, #embed и прочее.
RAII. Приведу пример кода.
Вполне может вылететь исключение из второго вызова new. Тогда получится так, что delete для ptr никогда не вызовется. Вот и утечка нарисовалась. Разумеется, так вылететь может не только внутри new, но и много где ещё (хотя оно и не вылетит при использование "Си с классами"). Хотя если рассматривать сценарий, где нехватка памяти это сразу полное завершение программы и там и там, то разницы особой не будет.
Некоторые проекты с модулями писать можно. Но постоянно приходится натыкаться на гору багов.
Раст сам по себе не лучший язык. Даже шаблонов из C++ нет. Вся работа с кодогенерацией работает через его макросы, которые работают с текстом, а не с типами. Всё ли в расте с аналогом constexpr хорошо? https://godbolt.org/z/sn3cbPYKz и даже не заифать https://godbolt.org/z/TGaGPP37c
В расте нет и исключений (они как-бы есть в виде паник, но кто их применяет везде?).
Перегрузок в расте нет.
Рефлексии в расте тоже нет, когда в плюсах она есть.
Если в язык закидывать всё подряд, то язык долго жить не будет. Нужно грамотно фичи добавлять, чтобы язык не разваливался посередине.
Стандарт и так разрешает оптимизировать код. Компилятор имеет право вырезать лишние конструкторы, деструкторы и прочее. Только есть разница в том, что считается наблюдаемыми эффектами. Если аллокации такими не считать, то компилятор будет иметь право работать и с длинными std::string (когда не срабатывает SSO).
Первый раз слышу про такие проблемы. Но у меня и с виндой опыта разработки нет, 100% кода пишу на лине.
Не такое уж и значительное преимущество.
Как Вы это в стандарт описывать собираетесь? И зачем так делать?
Так дело в том, что мне вообще нет дело до dll-ек. Я подключаю библиотеку в CMake, а дальше уже само находит и подключает. До уровня .dll опускаться смысла нет.
Nix это не только язык, но ещё и ОС и пакетный менеджер. nixos.org.
CPM.cmake это просто вспомогательные cmake скрипты для подключения пакетов. Используется, к примеру, в userver. https://github.com/cpm-cmake/CPM.cmake
Вы так и не ответили на то, а зачем нужно такое с C апи?
А можно увидеть какое-то более подробное описание алгоритма? И какое отношение имеет std::string к POD типам?
Суть в чём? Чтобы функция могла считаться constexpr по умолчанию? Но подозреваю, что могут возникнуть некоторые проблемы.
Зачем мне знать про какие-то там dll-ки? Какое отношения они вообще имеют к вопросу? Опять же, существуют CPM.cmake и nix, с которыми подключить библиотеку дело достаточно быстрое и удобное.
Предлагаете тогда не закидывать туда огромную кучу нормальных библиотек, а оставить только библиотеки без шаблонов? К примеру, Boost.Asio много использует шаблоны достаточно много, range-v3, ctre и т.д.
Зачем вообще он нужен будет тогда, когда там нет огромного количества полезных и используемых библиотек?
Зачем вообще ограничиваться только теми, у которых есть и C-апи? Плюсы это ведь не "Си с классами", а сильно более продвинутый язык.
Не наблюдаю тут никакой прозрачности. Можете рассказать про неё подробнее?
На что ниже у меня там тоже есть свой комментарий.
Но что будет если пойти по обратному пути уменьшения возможностей? Получится такой мерзотный язык как Go. Язык должен быть удобным инструментом для использования, а не удобным для освоения. В том же Go пошли обратным путем и сделали язык очень простым, но и максимально неудобным. Те же генерики ввели вот только недавно, а ведь такие средства для создания общего кода максимально полезная вещь.
Как по мне, в плюсах возможностей всё ещё недостаточно, а с каждым стандартом язык становится лучше. Но и возможности ради возможностей тоже не будут хороши. Они должны хорошо вписываться в язык, не должны приводить к бессмысленному росту языка, но должны приводить к его развитию. Лучше добавлять общие и гибкие решения, а всё остальное наслаивать уже на них, а не добавлять "адхоки" на каждый случай.
Вот такой код вполне себе разбирает. https://godbolt.org/z/ec91doMaY
Можете тогда предложить свой вариант того, как это могло бы выглядеть? Нужно ведь понимать, что std::string достаточно честный тип, который находится в стандартной библиотеке. Как это совместить со всем остальным в языке? К примеру, с тем же std::vector, std::variant, placement new и ручным управлением времени жизни объекта. constexpr и так получается практически автоматически, достаточно лишь constexpr приписать ко всему (да и if consteval блоки там, где компилятор не может вычислить куски в constexpr, но это более редкий случай).
Boost уже стал второй стандартной библиотекой. Если не важно то, что сам Boost это достаточно тяжелая зависимость, то можно брать его, хотя и не всё там сделано в самом удобном виде, да и не всё есть.
Про имена функций непонятно (буду дальше описывать что если бы Вы предлагали обязать их работать и через C апи в том числе). А что Вы предлагаете делать с библиотеками вроде
Boost.Hana, которые целиком на шаблонах сделаны? С таким подходом нельзя будет делать фактически ничего, что делает C++ самим собой. C++ без исключений, шаблонов и constexpr уже сложно назвать C++. С пакетными менеджерами уже давно естьCPM.cmake, да и можно использовать пакетные менеджеры ОС. У некоторых из них возможностей вполне достаточно. К примеру,nix.Так и не ясно, чем это было бы лучше подхода с constexpr сейчас. С деталями тоже неясно. Так и в предоставленном варианте нет возможности в этом блоке переиспользовать другие элементы, которые были вычислены в предыдущих. Оно всё равно потребует в данных фиксации того, что они вычислены на этапе компиляции, ведь компилятору это определить нетривиально, т.к. проблема останова и прочее.
Уже есть в виде std::meta::info и любых контейнеров, которые умеют хранить значения (к примеру, std::vector). Смотреть рефлексию для C++26.
Реализовывать Вы это как собрались? Предположим, что в таком блоке для "выражений" будет добавлено что-то, что закроет функцию и начнёт новую. Тогда реализация крайне нетривиально. Но есть и более важная проблема. В C++ шаблонах компилятор может заранее парсить их тело, ещё до их инстанцирования. Если там будут произвольные варажения, то это сломает эту модель и до инстанцирования о шаблонах нельзя будет сказать практически ничего.
Хорошо, предположим, что они такие блоки могут возвращать только специальные куски AST. Тогда эта модель с некоторыми изменениями кое-как, но работать может, хотя и сильно сузит возможности для кодогенерации. Но возникает один вопрос зачем это всё? Намного более гибким будет подход с возможностью задать новую функцию/тип/прочее через расширение к compile time рефлексии в C++26 с нужным именем, а после уже определить там тело через типы плюсового AST в стандартной библиотеке (можно и немного сахара для такого придумать, но необязательно).
Можно было бы изучить вопрос получше, а не сразу выкидывать про это на Хабр? Или Вы воспринимаете Хабр как место получения критики идей и их развития? Если так, то это имеет смысл. Но в итоге это должно не на Хабре дальше быть с таким, а развивать до зрелости подхода и пойти в пропозал для стандарта.
Нечто такое должно быть можно. Есть и template for.
Будет ли C++ тогда функциональным языком?
В C++ есть std::ranges::views, которые позволяют лениво обрабатывать это всё. В C++ в компиляторах есть оптимизации хвостовой рекурсии, пусть и не гарантированные. В стандартной библиотеке есть std::expected и std::optional.
Есть и вот такое:
Типы все неизменяемы, а метапрограммирование с типами фактически обязано быть в ФП.
Есть нормальный const.
Хотя исключения из плюсов особо не вырезать (хотя некоторые проекты и работают без исключений, это не полный язык). Но такая ли проблемы исключения? Они позволяют нормально композировать функции, тогда как с ошибками через монады бы так не вышло. Взять те же std::ranges как пример библиотеки. Если использовать std::expected, то эта библиотека фактически обязана была бы везде брать std::expected, а после автоматически их соединять. Таким образом, на границе интерфейса библиотек всегда нужно было бы прописывать std::expected апи, везде прописывать не самый удобный апи для работы с ним.
К тому же, исключения можно сделать и через алгебраические эффекты, но не в C++.
Про синтаксическую поддержку монад. Из коробки есть разве что корутины, которые позволяют выразить некоторые монады вроде Either или Maybe, но возможности всё равно достаточно ограничены. Но есть и сторонние библиотеки для создания синтаксической поддержки. У меня про неё написаны 2 статьи.
Паттерн матчинга и правда нет. Но есть std::variant с паттерном overloaded. Что позволяет использовать перегрузки (родное средство языка, между прочем) для "матчинга" по std::variant.
Для такого случая правила overload resolution с концептами тривиальны. С концептами там меньше повторений кода будет.
Больше символов?
else if constexpr(std::is_same_v<T, V1>) {}против,[&](V1& v){}. И это в случае явного перечисления через перегрузки. Если с концептами, то там сильно меньше получается.Вариант оригинальный (про него же речь?) то справляется. Но элегантным его точно не назовёшь. Про проблемы варианта через енам сказано.