Одна выполняется во время компиляции (макросы тоже например)
Макросы не в тему от слова совсем. Есть программа, она выполняется, в процессе выполнения меняет себя. Да, в compile-time. Поэтому и называется compile-time рефлексия.
Но выполнение есть. Что и требовалось в том определении, на которое вы сослались.
программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.
Т.е. когда вот такой код модифицирует структуру программы:
consteval auto parse(std::string_view key, int value)
-> std::meta::info
{
using std::meta::reflect_constant;
std::vector<std::meta::info> members;
std::vector<std::meta::info> inits;
members.push_back(reflect_constant(
data_member_spec(^^int, {.name=key})));
inits.push_back(reflect_constant(value));
auto type = substitute(^^Cls, members);
inits.insert(inits.begin(), type);
return substitute(^^construct_from, inits);
}
то никакого "выполнения", надо полагать, нет. Получается, что программист написал код на C++, этот код выполнился, породил в процессе своей работы новые структуры данных и объекты, но за "выполнение" это не считается. Вероятно, функция parse в коде не вызывается, и результат ее работы ни к чему не приводит. Вот прям "не верь глазам своим" (С) К.Прудков.
Статья о том, что сделано в C++ для того, чтобы решать проблемы C++ников, но вы пришли сюда что бы порассуждать о том, как может быть в других языках? Конструктивно, что тут еще скажешь.
И иллюстрируют тот факт, что никакой анализ текста программы, как бы его не называть, для этого не нужен.
Тогда покажите как это сделать именно в C++. Ведь не нужен же это, как его, "никакой анализ текста программы". Вот и продемонстрируйте мастер-класс.
А именно, для того, чтобы программа могла исследовать и менять свою собственную структуру на этапе выполнения, приспосабливая её к своим входным данным.
Вы смешиваете в одну кучу и рефлексию, и динамическую кодонерацию, и горячую замену кода.
А "рефлексия на этапе компиляции" – это просто оксюморон.
И? С++никам теперь стоит собраться и написать пропозал в комитет для изъятия рефлексии из языка потому что г.@vadimr объявил её оксюмороном в комментариях на Хабре?
Для компилятора сам текст программы является входными данными.
Только вот речь идет не о компиляторе, а о программе, которая в момент компиляции может анализировать сама себя и менять себя в момент этой самой компиляции подстраиваясь под текущие условия.
Те примеры, которые вы привели, являются просто обычными парсерами, подключаемыми на этапе компиляции.
Во-первых, не всегда удобно подключать что-то внешнее на этапе компиляции. Именно поэтому мы и стали делать свой PEG-парсер.
Во-вторых, покажите как получить strong typedef посредством "парсера, подключаемого на этапе компиляции".
Простите, но я вот в этой вашей фразе "Вполне возможно, году к 2030 в компилятор C++ вкорячат полноценный парсер и генератор AST, который сможет делать почти всё как в Лиспе за 70 лет до того. Только не забыть бы в процессе, зачем вообще это было нужно. А вроде уже забыли, судя по таким примерам." вопроса не увидел.
Так вы знаете зачем нужна рефлексия или у меня спрашиваете?
С ваших слов, это
С моих слов это не это.
Статья показывает как взять некое описание и из этого описания сгенерировать набор C++ных структур + набор объектов с конкретными значениями. Что может быть использовано для ряда задач из категории eDSL. Например, генерация эффективных разборщиков регулярных выражений.
Или генераторов лексических/грамматических парсеров. Например, нам пришлось сделать свой парсер на базе PEG. Только для этого пришлось упороться шаблонами, что привело, как минимум, к двум неприятным последствиям:
Трудно это дело отлаживать.
Даже для небольших грамматик создаются такие длинные имена типов, что в выхлопе компилятора при ошибке одно имя может занимать целый экран. И есть большие сомнения, что компилятор в принципе осилит грамматики размером побольше.
В то же время, если в compile-time распарсить eDSL с тем же PEG выражением, то может получиться гораздо проще и удобнее.
Еще один потенциальный пример из нашей же истории. Использовать аналог express-js в C++ ну такое себе: работает, но часть ошибок, которые в потенциале можно было бы ловить прямо в compile-time, перекладывается на run-time. Можно сделать с большими гарантиями по безопасности по типам, но получается так себе по эргономике и сложно в реализации. Если же описание end-point-ов делать специальными строками в аннотациях методов, то можно и сильно улучшить эргономику, и (сильно надеюсь) упростить реализацию.
Это если говорить о грядущей рефлексии именно в стиле -- взять внешнее описание чего-нибудь и сгенерировать из него набор C++ных сущностей. Если же говорить о других применениях рефлексии, то из совсем свежих впечатлений: в C++ нет strong typedef и вряд ли в обозримом будущем появится. Но на базе рефлексии можно сделать собственный лисапед, который позволит написать что-то вроде:
using first_type = my_strong_typedef_for< std::map<int, std::string> >;
using second_type = my_strong_typedef_for< std::map<int, std::string> >;
static_assert(!std::is_same_v<first_type, second_type>);
Любите востребованность – пишите на Питоне.
Не говорите людям что им делать и им не придется говорить вам куда отправиться.
Во-первых, какой такой программы. В статье описан потенциальный механизм, который пока не реализуем из-за отсутствия полноценных compile-time парсеров (хоть JSON-а, хоть еще чего-нибудь). Т.е., с моей точки зрения, это нельзя рассматривать как готовый "рецепт", а просто как proof-of-concept для того, чтобы разработчики могли осознать мощь новых возможностей.
Во-вторых, это в данном proof-of-concept из json-а генерируются объекты с конкретными значениями, что и дает вам возможность доколупаться до воображаемого вами хардкодинга. Но таким же образом могут генерироваться и структуры по внешним спецификациям (наприимер, из .proto-файлов или из описаний Swagger-а). И тогда содержимое внешних файлов спецификаций не может просто так поменяться. А если меняется, то под новую спецификацию и логику нужно будет подправить.
Так что нет, фатальных проблем с "хардкодингом" я лично в тексте не вижу.
Чей деструктор? У HANDLE деструктора нет. А объект, спрятанный за уродским макросом defer, появляется только после if-ов. И дело до этого объекта может не дойти.
есть то преимущество, что нет шансов добавить какой-то код между получением хэндла и созданием очищающего объекта для этого хэндла. У нас либо есть proc, для которой будет вызван деструктор, либо нет.
И попробуй такую потенциальную утечку ресурсов затем найти.
ЗЫ. unique_ptr далеко не всегда подходит для очистки ресуров, для таких целей можно сделать небольшую универсальную обертку над разнотипными хэндлами. Вот здесь я когда-то давным-давно показывал пример класса handle_holder.
ЗЗЫ. Ув.тов.Kelbon правильно в своем первом комментарии указал про копирование лямбды. Это серьезный просчет.
Пока что больше похоже на то, что он ведет себя как Д'Артаньян, который весь в белом один стоит красивый, а остальные все в известной субстанции (особенно разработчики C++ и стандартной библиотеки C++). Ему бы отдельную статью на Хабре написать с основными тезисами своих изысканий, и уже оттуда бы давать ссылки на свой сайт со своими работами. Чтобы было отдельное место где автору объяснили бы в чем он не прав (если бы нашлись желающие).
Нет, я просто более чем толсто намекаю на то, что ваше высказывание "И даже смешно, что критик не осиливает 1 строчку кода без ошибок написать" мягко говоря не соответствует действительности.
Расследование показало, что cpp.sh использует clang, и на годболте тоже ошибку он видит.
И? Есть широко используемый компилятор, который не видит. Сделать пример, в котором и clang не будет видеть не проблема от слова совсем.
По моему, в С++ давно идёт борьба со скрытыми программисткими ошибками в области надежности, но граблей остаётся предостаточно.
Вы это автору тезиса про "Откуда возьмутся такие ошибки в С++, если на нём писать как на С++, а не как на С?" объясните.
И, сначала надо прочитать учебники, гайдлайны, стандарт только для сокращения количества ошибок.
Вот честно, складывается ощущение, что вы уже забыли когда в последний раз писали код. Ибо если бы вы это делали, то знали бы, что есть огромное количество ошибок, допущенных по невнимательности (да тех же опечаток), от которых не спасает штудирование учебников/гайдлайнов/стандартов.
Тогда как принципиальные ограничения языка (как в Rust-е, скажем), от некоторого процента таких ошибок защищают. В отличии от C++.
Что касается якобы моего утверждения, то оно относилось только к тому, что C++ не обещал решить все проблемы работы с памятью из Си.
Более того, реальных проектов, где использовались бы -Wall и -Wextra не так уж и много. Просто потому, что в реальных проектах используются сторонние либы, а их авторы не заморачиваются на то, чтобы код компилировался без предупреждений на высоких уровнях. Да и слишком накладно достижение этого, особенно если нужно поддерживать и gcc, и clang, и msvc. Достаточно подключить в проект asio или fmtlib, чтобы получить кучу предупреждений при наличии -Wall.
Ну и главное: пример-то тривиальнейший, не так уж сложно сделать пример, в котором наивный анализ от компилятора ничего не выявит.
std::string_view было бы правильно для компилятора .
Ну вот как раз std::string_view<char> -- это неправильно для компилятора, т.к. std::string_view -- это псевдоним для std::basic_string_view<char>, этот псевдоним не может быть чем-либо параметризован.
Но неправильно применено.
Так именно в этом и смысл примера: код, на который компилятор не ругается, но который является принципиально ошибочным. И эта та самая ошибка памяти, о якобы невозможности которой здесь выше кто-то заикнулся.
Макросы не в тему от слова совсем.
Есть программа, она выполняется, в процессе выполнения меняет себя. Да, в compile-time. Поэтому и называется compile-time рефлексия.
Но выполнение есть. Что и требовалось в том определении, на которое вы сослались.
Т.е. когда вот такой код модифицирует структуру программы:
то никакого "выполнения", надо полагать, нет. Получается, что программист написал код на C++, этот код выполнился, породил в процессе своей работы новые структуры данных и объекты, но за "выполнение" это не считается. Вероятно, функция parse в коде не вызывается, и результат ее работы ни к чему не приводит. Вот прям "не верь глазам своим" (С) К.Прудков.
Статья о том, что сделано в C++ для того, чтобы решать проблемы C++ников, но вы пришли сюда что бы порассуждать о том, как может быть в других языках? Конструктивно, что тут еще скажешь.
Тогда покажите как это сделать именно в C++. Ведь не нужен же это, как его, "никакой анализ текста программы". Вот и продемонстрируйте мастер-класс.
Каким боком ваши упражнения в Лиспе относятся к C++?
Годы идут, лисперы не меняются.
Вы смешиваете в одну кучу и рефлексию, и динамическую кодонерацию, и горячую замену кода.
И? С++никам теперь стоит собраться и написать пропозал в комитет для изъятия рефлексии из языка потому что г.@vadimr объявил её оксюмороном в комментариях на Хабре?
Только вот речь идет не о компиляторе, а о программе, которая в момент компиляции может анализировать сама себя и менять себя в момент этой самой компиляции подстраиваясь под текущие условия.
Во-первых, не всегда удобно подключать что-то внешнее на этапе компиляции. Именно поэтому мы и стали делать свой PEG-парсер.
Во-вторых, покажите как получить strong typedef посредством "парсера, подключаемого на этапе компиляции".
Простите, но я вот в этой вашей фразе "Вполне возможно, году к 2030 в компилятор C++ вкорячат полноценный парсер и генератор AST, который сможет делать почти всё как в Лиспе за 70 лет до того. Только не забыть бы в процессе, зачем вообще это было нужно. А вроде уже забыли, судя по таким примерам." вопроса не увидел.
Так вы знаете зачем нужна рефлексия или у меня спрашиваете?
С моих слов это не это.
Статья показывает как взять некое описание и из этого описания сгенерировать набор C++ных структур + набор объектов с конкретными значениями. Что может быть использовано для ряда задач из категории eDSL. Например, генерация эффективных разборщиков регулярных выражений.
Или генераторов лексических/грамматических парсеров. Например, нам пришлось сделать свой парсер на базе PEG. Только для этого пришлось упороться шаблонами, что привело, как минимум, к двум неприятным последствиям:
Трудно это дело отлаживать.
Даже для небольших грамматик создаются такие длинные имена типов, что в выхлопе компилятора при ошибке одно имя может занимать целый экран. И есть большие сомнения, что компилятор в принципе осилит грамматики размером побольше.
В то же время, если в compile-time распарсить eDSL с тем же PEG выражением, то может получиться гораздо проще и удобнее.
Еще один потенциальный пример из нашей же истории. Использовать аналог express-js в C++ ну такое себе: работает, но часть ошибок, которые в потенциале можно было бы ловить прямо в compile-time, перекладывается на run-time. Можно сделать с большими гарантиями по безопасности по типам, но получается так себе по эргономике и сложно в реализации. Если же описание end-point-ов делать специальными строками в аннотациях методов, то можно и сильно улучшить эргономику, и (сильно надеюсь) упростить реализацию.
Это если говорить о грядущей рефлексии именно в стиле -- взять внешнее описание чего-нибудь и сгенерировать из него набор C++ных сущностей. Если же говорить о других применениях рефлексии, то из совсем свежих впечатлений: в C++ нет strong typedef и вряд ли в обозримом будущем появится. Но на базе рефлексии можно сделать собственный лисапед, который позволит написать что-то вроде:
Не говорите людям что им делать и им не придется говорить вам куда отправиться.
Ваше мнение очень важно для всех нас, продолжайте держать нас в курсе.
Лиспу это очень сильно помогло. Такой востребованный в последние 30 лет язык, что прям образец для подражания.
И зачем же?
Во-первых, какой такой программы. В статье описан потенциальный механизм, который пока не реализуем из-за отсутствия полноценных compile-time парсеров (хоть JSON-а, хоть еще чего-нибудь). Т.е., с моей точки зрения, это нельзя рассматривать как готовый "рецепт", а просто как proof-of-concept для того, чтобы разработчики могли осознать мощь новых возможностей.
Во-вторых, это в данном proof-of-concept из json-а генерируются объекты с конкретными значениями, что и дает вам возможность доколупаться до воображаемого вами хардкодинга. Но таким же образом могут генерироваться и структуры по внешним спецификациям (наприимер, из .proto-файлов или из описаний Swagger-а). И тогда содержимое внешних файлов спецификаций не может просто так поменяться. А если меняется, то под новую спецификацию и логику нужно будет подправить.
Так что нет, фатальных проблем с "хардкодингом" я лично в тексте не вижу.
А если этот json производится какой-то внешней тулзиной, к исходникам которой у вас даже доступа нет?
Чей деструктор? У HANDLE деструктора нет.
А объект, спрятанный за уродским макросом defer, появляется только после if-ов. И дело до этого объекта может не дойти.
У варианта со вкусом unique_ptr:
есть то преимущество, что нет шансов добавить какой-то код между получением хэндла и созданием очищающего объекта для этого хэндла. У нас либо есть
proc
, для которой будет вызван деструктор, либо нет.Тогда как в варианте с defer:
Запросто можно добавить еще один if перед defer и получить приключения:
И попробуй такую потенциальную утечку ресурсов затем найти.
ЗЫ. unique_ptr далеко не всегда подходит для очистки ресуров, для таких целей можно сделать небольшую универсальную обертку над разнотипными хэндлами. Вот здесь я когда-то давным-давно показывал пример класса handle_holder.
ЗЗЫ. Ув.тов.Kelbon правильно в своем первом комментарии указал про копирование лямбды. Это серьезный просчет.
Пока что больше похоже на то, что он ведет себя как Д'Артаньян, который весь в белом один стоит красивый, а остальные все в известной субстанции (особенно разработчики C++ и стандартной библиотеки C++). Ему бы отдельную статью на Хабре написать с основными тезисами своих изысканий, и уже оттуда бы давать ссылки на свой сайт со своими работами. Чтобы было отдельное место где автору объяснили бы в чем он не прав (если бы нашлись желающие).
Не люблю минусовать комментарии и стать на Хабре, но ваши комментарии этого таки заслуживают.
Нет, я просто более чем толсто намекаю на то, что ваше высказывание "И даже смешно, что критик не осиливает 1 строчку кода без ошибок написать" мягко говоря не соответствует действительности.
Я знаю на что вы отвечали и именно поэтому указываю на то, что C++ и не обещал решить проблемы по работе с памятью из Си.
И? Есть широко используемый компилятор, который не видит. Сделать пример, в котором и clang не будет видеть не проблема от слова совсем.
Вы это автору тезиса про "Откуда возьмутся такие ошибки в С++, если на нём писать как на С++, а не как на С?" объясните.
Вот честно, складывается ощущение, что вы уже забыли когда в последний раз писали код. Ибо если бы вы это делали, то знали бы, что есть огромное количество ошибок, допущенных по невнимательности (да тех же опечаток), от которых не спасает штудирование учебников/гайдлайнов/стандартов.
Тогда как принципиальные ограничения языка (как в Rust-е, скажем), от некоторого процента таких ошибок защищают. В отличии от C++.
Что касается якобы моего утверждения, то оно относилось только к тому, что C++ не обещал решить все проблемы работы с памятью из Си.
Ну вот вам еще godbolt для gcc 14.3 с
-Wall -Wextra -Wpedantic
: https://godbolt.org/z/q7TEdvc3xНе ругается.
Аналогично с wandbox: https://wandbox.org/permlink/rYq6VFom5zNtmzYt
Нет ни ошибок, ни предупреждений.
Более того, реальных проектов, где использовались бы
-Wall
и-Wextra
не так уж и много. Просто потому, что в реальных проектах используются сторонние либы, а их авторы не заморачиваются на то, чтобы код компилировался без предупреждений на высоких уровнях. Да и слишком накладно достижение этого, особенно если нужно поддерживать и gcc, и clang, и msvc. Достаточно подключить в проект asio или fmtlib, чтобы получить кучу предупреждений при наличии-Wall
.Ну и главное: пример-то тривиальнейший, не так уж сложно сделать пример, в котором наивный анализ от компилятора ничего не выявит.
Ну так посмотрите к какому именно комментарию был дан ответ с примером со string_view: https://habr.com/ru/articles/913702/comments/#comment_28373696
Увидите, что к тому самому, в дочерних к которому мы сейчас и находимся.
А никто и не говорил, что он есть в статье.
Некто @dv0ich заявил буквально следующее:
И ему дали пример того "откуда возьмутся". Я бы и сам примеров накидал, но было лень в 100500-ый раз повторять очевидное.
Правда? Да что вы говорите!
https://godbolt.org/z/G1fKf9ss1 -- покажите пальцем на ошибку от компилятора, плиз.
Так он и не собирался это делать.
Ну вот как раз
std::string_view<char>
-- это неправильно для компилятора, т.к. std::string_view -- это псевдоним дляstd::basic_string_view<char>
, этот псевдоним не может быть чем-либо параметризован.Так именно в этом и смысл примера: код, на который компилятор не ругается, но который является принципиально ошибочным. И эта та самая ошибка памяти, о якобы невозможности которой здесь выше кто-то заикнулся.
И да, это как раз таки ошибка памяти, хоть вы так и не считаете:
После таких заявлений мнение "экспертов" можно сходу отправлять в /dev/null.
Как раз в данном случае было более чем не очевидно. Но стало очевидно, что в предмете вы не разбираетесь, однако мнение имеете.