Как стать автором
Обновить

Комментарии 84

Насколько это увеличивает время компиляции?

Какая разница?

Ну вообще-то это прямая потеря времени программиста.

Вы путаете программиста с машинисткой.

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

А мне лично всё равно, потому что три секунды на компиляцию — это ничто, а если про десять минут — то даже это ничто, потому что я выйду на балкон и выкурю сигарету.

Особенно продуктивным день становится когда после каждого изменения надо ждать 10 минут компиляции.

Или ждать по 8-10 часов пока все проверки в мердж реквесте скомпилируются и пройдут.

после каждого изменения надо ждать 10 минут компиляции

Любопытно, у меня компиляция проходит за 3-8 секунд, но даже в таких условиях я ее запускаю не чаще, чем три раза в день.

ждать по 8-10 часов пока все проверки в мердж реквесте скомпилируются и пройдут

Проблема не в самой компиляции, мне кажется.

Проблема не в самой компиляции, мне кажется.

Для крупного проекта это обычное дело. Особенно если требуется чистый билд

Ну, я-то, конечно, только тудушечки кропаю по 20 строчек. Поэтому спросим знатока крупных проектов: а зачем ждать завершения чистого билда?

Некоторые проекты требуют traceability of requirements Грубо нужно доказать что условие «программа выполняет расчет Х с точностью У». Чтобы доказать нужно показать весь путь от первой строчки кода до последнего прошедшего теста, и чистый билд в таком случае это одно из доказательств. Это не нужно делать на каждом комитете само собой, но ждать 8 часов в день релиза так себе удовольствие, особенно когда времени итак нет.

А поставить его в ночь перед релизом вам запрещает что именно?

Я не пересобираю llvm по нескольку раз в день.

А в своих проектах — не пишу весь код прямо в заголовочные файлы.

Я по работе правлю локальный бранч llvm.

Ну вы же понимаете, что статистически — вас не существует?

:)

Написали бы про это текст, я бы его с радостью и удовольствием почитал, кстати. Да и в карму плюсик смог бы поставить.

Вот тут немножко (в основном там про то, что уже есть в llvm). Много букв на естественном языке писать лениво )

А статистику можно по разному наводить, здесь все подряд (и даже девочки) контрибутят в llvm.

Спасибо!

Повторно изобрели наиболее бесполезную фичу языка PL/I (1964).

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

Идея самомодифицирующегося кода сама по себе богатая, но не во время компиляции же.

Повторно изобрели наиболее бесполезную фичу языка PL/I (1964).

Разве не constexpr из C++?

Ну там функции общего вида нельзя писать. Скорее это хинт оптимизатору.

Хотя в конечном итоге ноги оттуда же растут.

goto сделать попробуйте.

Но вообще - в C++ здесь тоже сильный прогресс в этом бесполезном направлении.

goto сделать попробуйте.

goto и не в constant evaluated context-то могут не только лишь все. А в сабже goto вообще нет.

в этом бесполезном направлении.

Говорите за себя. Если вы так и не научились этим пользоваться, а вместо этого предпочитаете лепить наколеночные скрипты для кодогенерации и вызывать их через мейкфайлы - это еще не значит, что оно бесполезно.

Ну раз вы научились этим пользоваться, то вам, наверное, не составит труда привести практический пример.

Что касается меня лично, то я вообще предпочитаю средства с совсем другой концепцией генерации и исполнения кода, но это не имеет отношения к делу.

Ну раз вы научились этим пользоваться, то вам, наверное, не составит труда привести практический пример.

Вам уже и без меня тут привели ряд практических примеров, но не в коня корм - вы продолжаете твердить, что слаще морковки ничего не бывает, а наколеночные скрипты, кодогенерирующие "конфиги" на целевом языке (а другим способом обрабатывать их в compile-time без задействования встроенных в язык средств compile-time выполнения не получится) - это очень удобно. Еще раз - говорите за себя.

Чем в вашем понимании наколеночный скрипт отличается от ненаколеночного? Одобрением WG21?

В моём понимании "наколеночным скриптом" является как раз скорее constexpr, когда язык C++ используется для макрогенерации (или, говоря более общо, символической продукции), к которой он совершенно не приспособлен.

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

Когда у тебя язык — голое AST, компилятору становится всё равно, куда, когда и как компилировать.

В этом и сила )

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

Вы не понимаете зачем нужно метапрограммирование в целом или зачем нужен его конкретный подвид в виде функций, вычисляемых на этапе компиляции?

На этапе компиляции, конечно. Да ещё в таком виде, когда эти функции не могут порождать собственно синтаксические конструкции языка второго этапа. Что мог язык препроцессора PL/I, но даже это ему не помогло.

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

У вас компилятор не умеет оптимизировать константный вызов стандартной функции lcm? Думаю, что это не та проблема, которую стоило бы решать таким вычурным способом.

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

Без порождения синтаксических конструкций языка — действительно, не очень понятно, зачем оно нужно. Но, вероятно, они рано или поздно к этому придут (сначала, правда, придется решить врожденную проблему «абстрактное синтаксическое дерево не нужно на уровне стандартной библиотеки»).

Но вот макросы в эликсире, порождающие AST, внедряемый по месту вызова на этапе компиляции — это буквально то, что сделало 90% моих OSS-библиотек в принципе возможным. PL/1 не умер из-за этой возможности, а прожил так долго из-за нее. Погубили его попытки вообще всё унести в препроцессор, из-за чего он превратился в монстра, которого практически никто не мог поддержать. Это все равно как после добавления DSL из языка убрать всё, что не DSL.

Я не говорил, что PL/I умер из-за этой возможности. Я говорил, что почти никто на практике ей не пользовался. Вот просто буквально реальные программы на PL/I не включали шаг препроцессора при трансляции. Там же просто всё было.

почти никто на практике ей не пользовался

Ну не знаю, у меня недостаточно данных для обобщения. На практике в Физтехе им. Иоффе у нас было что-то PL/1, и это был не Фортран только из-за препроцессора.

Для порождения синтаксических конструкций в расте есть макросы. Если простой паттерн-матчинг не вывозит, то процедурные макросы. Получаете поток токенов на вход, выплевываете поток токенов на выход

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

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

В каком смысле «неизвестно»? Как на стадии компиляции может быть что-то неизвестно?

Я же сказал: это родовая травма, если бы про то, чтобы сделать AST — first class citizen — подумали заранее, таких бы проблем не было бы, и быть не могло.

Это даёт возможность указания констант в том формате, в котором их указывать удобно - а не в том формате, в котором их указывать получилось.

Тут надо отделить мух от котлет. Вычисление константных выражений на этапе компиляции – конечно, полезная вещь, однако вообще не требует поддержки в синтаксисе языка, так как является просто оптимизацией компилятора. Что же касается программирования алгоритмов, исполняемых на этапе синтеза кода, то в том половинчатом виде, как это представлено в PL/I (и ещё более урезано – в Rust и C++) найти этому реально ценное практическое применение очень сложно.

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

Можно привести какой-нибудь практический пример, когда это было бы необходимо?

Особенно учитывая, что никто вообще-то вам не гарантирует компиляции непосредственно в конечный исполняемый железом бинарный код.

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

Давайте начнём с того, что constexpr сам по себе не гарантирует, что его значение будет посчитано при компиляции. Там на эту тему придумали специальный constexpr! с восклицателным знаком, поэтому смысл обычного constexpr по-прежнему ускользает.

Далее, вряд ли даже с constexpr! на практике вы таким микроменеджментом вытянете все грехи компилятора и сильно улучшите оптимизацию своей программы. Сомнительно, чтобы всё её быстродействие держалось только на вычислении одной константы. Поэтому вопрос оптимизации нужно рассматривать в целом.

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

С моей точки зрения, медленная программа определённо лучше, чем никакая.

Когда как. Если есть вариант сменить компилятор или исправить ошибки в исходниках - то "никакой" программа долго не пробудет. А вот медленной программа может быть годами.

Пример из моей практики работы над криптографической библиотекой: знание на этапе компиляции определенных констант (если точнее: целых таблиц констант) - позволяет компилятору генерировать код, который лучше векторизуется и работает на 10-15% быстрее, чем код, скомпилированный с кодогенерацией)

Это само по себе понятно, вопрос в том, нужно ли эти константы генерировать через constexpr. Так-то и CRC обычно считают по таблице.

Нужно. Позволяет избежать глупых ошибок в константах.

Это, между прочим, ещё вопрос, где ошибку будет проще допустить и особенно потом найти – в константах, в генерирующем их алгоритме или в редко используемой процедуре макрогенерации в компиляторе.

Разумеется, в константах. Алгоритм можно протестировать, а ошибки в компиляторе пусть ищут разработчики компилятора.

Такое работает, если исходный код удобно обрабатывать средствами языка. Естественно, я про Лисп )

Если конфиги защиты в код, какой от них смысл... Если вам нужен факториал 5 посчитайте его и напишите сразу 120 а не тратьте время на написание кода... Примеры оторваны от жизни...

Вот вам более интересный пример тогда (правда, на плюсах) - выражение вычисляется во время компиляции, как в примере "Мини-интерпретатор" из этой статьи, но берется оно при этом из внешнего текстового файла.

Не проще при такой надобности вставить скрипт в мейкфайл?

Ну т.е. вместо использования встроенных в язык средств вы хотите делать на коленке собвственную систему метапрограммирования на скриптах и мейкфайле. А зачем?

  1. Это проще.

  2. Это не отнимает время при каждой компиляции, в силу принципа работы мейкфайла.

  3. Это общепринятая практика.

Скорее, тут уместен вопрос, в чём была цель тащить это в компилятор.

  1. Это намного сложнее, особенно если нужно обеспечить безопастност выполенения и интеграцию с существующими структурами.

  2. Современным компиляторам тоже не нужно перевычислять constexpr каждый раз.

  3. Нет, это практика времен, когда не было нужных иструментов.

в чём была цель тащить это в компилятор.

Чтобы не надо было писать уродливые скрипты кодо-генерации.

Нет, это практика времен, когда не было нужных иструментов.

Тут проблема в том, что сам C++ – в известной степени практика времён, когда не было нужных инструментов.

Не проще при такой надобности вставить скрипт в мейкфайл?

И вот уже у вас есть, помимо исходного кода, какой-то мейкфайл рядом, и вместо программирования на одном языке вы вынуждены писать программу сразу на двух.

Тоже показалось, что примеры не из жизни. Хотелось бы увидеть как решается хоть какая-то не надуманная проблема.

Ну вот у вас есть код с линейной алгеброй и надо для заданного ядра вычислить обратную матрицу - не очень удобно для каждого случая делать это руками и вписывать константы в код. А если потом еще понадобилось транспонировать, то опять, вместо того, чтобы написать функцию в 3 строчки, надо вручную все считать и постраться нигде не ошибиться - звучит не очень. А если мне надо рассчитывать значение в зависимости от размера структуры - тоже руками делать и потом не забыть поменять когда добавится еще одно поле?

Вписывать константы в код – вообще так себе практика. Поэтому ядро вам целесообразно будет вынести в какой-то файл настроек. А если так, то в чём смысл пересчитывать этот файл настроек именно при компиляции основного расчёта?

Чтобы у вас был "файл настроек", нужно чтобы была как минимум файловая система, а это совершенно не всегда так. Я уже не говорю про оверхэд на чтение и парсинг, который тоже не всегда допустим. Ну и как вы этот файл создавать планируете, опять самописным скриптом?

Ну исходный код своей программы вы же как-то создаёте, где-то храните и чем-то обрабатываете? Чем файл настроек хуже (а я, заметьте, нигде не говорю, что файл настроек обязательно надо обрабатывать в рантайме)?

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

Я так понял, что имеется в виду embedded - собрали бинарь на компе, специальным тулчейном, залили на железку, на ней может не быть много чего.

Ну так этот тулчейн может и таблицу коэффициентов подготовить не хуже компилятора C++.

Как же в ембеддед-то жили всё это время без C++ и constexpr?

Вы возможно не осознаете, но constexpr функции, можно и вне constexpr контекста вызывать, те у вас что compile-time, что run-time вычисления гарантировано имеют одинаковый результат( потому что гарантированно вычисляются единым образом ).

Да, и как по мне sin(pi/4) - намного яснее чем 0.707 или SIN_45

Вы, возможно, не осознаёте, но для превращения sin(pi/4) в 0.707 не нужен constexpr.

гарантировано имеют одинаковый результат

А это просто неверно.

Даже на разных процессорах в рантайме могут получаться разные результаты.

>Вы, возможно, не осознаёте, но для превращения sin(pi/4) в 0.707 не нужен constexpr.

constexpr - как бы гарантирует это превращение. Умные компилчторы конечно и без всяких напоминалок подставят что необходимо.

>Даже на разных процессорах в рантайме могут получаться разные результаты.

А это уже, если я правильно мир воспринимаю, не из-за меня. Я код пишу на высокоуровневом языке, соответсвенно ожидаю, что один и тот же код будет везде работать одинаково.( предположим, никаких UB в моём коде нет ). Здесь возможно недопонимание моих слов, надеюсь поймете, что именно я имею ввиду.

ожидаю, что один и тот же код, будет везде работать одинаково

От создателей: «Проблема на вашей стороне, на моём лэптопе всё работает».

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

Осознаю, что в реальной жизни такого не бывает, конечно =)

Ну или я не прав, тогда поправьте меня.

В стандарте Си во многих случаях поведение зависит от реализации.

Да ладно поведение, буквально длина слова тоже зависит.

Скорее всего может, но вот мне видится такое преимущество:

в нашем условном тулчейне вычисления надо делать на, скорее всего, каком-то "птичьем" языке, который не вся команда одинаково хорошо умеет. А если это написано на языке, которым все в команде должны владеть, это упростит поддержку.

Тут такое дело. С++ – сам по себе язык очень объёмный. Если какой-то человек умеет держать в руках паяльник и писать на C++ программы для микроконтроллера, то это совсем не значит, что он должен владеть тем, что в C++ называется средствами метапрограммирования. И вполне возможно, что ему будет проще написать скрипт на каком-нибудь питончике, чем въехать в constexpr.

Мы же живём в мире реальных альтернатив, а не чистой идеологии.

Я лично вообще против моноязыковых сред, это фанатизм, он до добра не доводит.

Вполне может быть и так.

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

Если включить фантазию, то кому-то скрипт на питоне может показаться хорошей идеей, а кому-то - на lua (кому-то баш норм). Это не повод затаскивать в проект всё подряд. Тут решать лиду, наверное.

Я ещё и нить потерял, вроде статья про раст была :)

Но в целом, если вам не подходит какой-то солюшен, не значит, что другим разработчиками это тоже не вариант.

Вот именно потому средства метапрограммирования и нужно упрощать, и возможность выполнения самого обычного кода на Rust в const-контексте - как раз и есть такое упрощение.

Я согласен, что это упрощение синтаксиса Rust по сравнению с C++ – движение в правильном направлении. Однако семантика по-прежнему очень ограничена для практических применений.

К примеру, в Лиспе запятую (unquote) поставил – вот тебе и метапрограммирование. Но при этом:

– макроподстановка может порождать произвольный синтаксис основного языка, а не только значения данных;

– может выполняться макроподстановка в результате макроподстановки;

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

В таком виде метапрограммирование – действительно мощная вещь, позволяющая малыми силами реализовывать семантики высших порядков. А так, что просто цикл запустить в компайлтайме – ну, это как-то игрушечно.

Для метапрограммирования в стиле Лисп в языке Rust есть макросы. Однако, не всегда нужно манипулировать элементами синтаксиса языка, иногда нужно всего лишь посчитать несколько значений

В отдельных редких случаях может и полезно.
Еще полезнее было бы - пример, инициализация чтением файла на этапе компиляции, без встраивания всего файла. Пригодилось бы, например справочник какой из CSV/JSON подгружать.

Кстати, автор, а ты свои примеры сам-то пробовал запускать? Особенно парсеры?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий