LINUX.ORG.RU

Возможности метапрограммирования в С++

 , ,


0

3

Как в плюсах обстоят дела с метапрограммированием? Например такая задача: есть библиотека длинной арифметики, GMP называется. Даже есть привязка для С++ с перегруженными + - * / и прочими штуками. См https://gmplib.org/manual/C_002b_002b-Interface-General.html#C_002b_002b-Inte...
Так вот, допустим что надо сделать нечто, что делало бы оптимизацию операций с этими вот числами. Пример:

  if (a == 0) error();
  else return (a+a+a+a)/a;
Надо чтобы этот код был преобразован к виду
  if (a == 0) error();
  else return 4;
А например какой-нибудь код
 return a*c+b*c;
становился
 return c*(a+b);
и так далее.
Так вот. Чтобы решать такого рода задачи, придумали https://en.wikipedia.org/wiki/Expression_templates
Однако, код разбора и преобразования выражений в более оптимальный вид получается неуниверсальным - он работает только в компилтайме. Если я захочу в своей программе читать из stdin-а некую формулу, и эту формулу определенным образом преобразовывать, чтобы ее было легче считать, то эти expression templates оказываются совершенно бесполезными(можно разве что попробовать как-нибудь встроить непосредственно в программу кусок компилятора, который ответственен за разбор шаблонов, но это не является приемлемым решением). Так вот, можно ли как-нибудь сделать это универсально, чтобы и на этапе компиляции, и при выполнении я мог использовать ОДИН КОД для арифметических преобразований?
Единственный выход, который я вижу - метапрограммирование через кодогенерацию.

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

★★★★★

Последнее исправление: SZT (всего исправлений: 2)

facepalm:

// Пример:
  if (a == 0) error();
  else return (a*a*a*a)/a;
// Надо чтобы этот код был преобразован к виду
  if (a == 0) error();
  else return 4;

и кстати преобразование a*c+b*c => c*(a+b) годно только при GMP (да и то полностью не уверен), для прочих может сулить бооольшие неприятности.

MKuznetsov ★★★★★
()

если был бы какой-нибудь язык программирования без GC, такой же низкоуровневый и быстрый как Си, и с гомоиконностью, чтоб можно было работать с неким абстрактным представлением кода вместо генерации синтаксически правильного Си/С++ кода

«вы только что прослушали заклинание вызова лисперов в тред»

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

Да, там надо заменить на сложение, счас поправлю

SZT ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

и кстати преобразование a*c+b*c => c*(a+b) годно только при GMP (да и то полностью не уверен), для прочих может сулить бооольшие неприятности.

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

SZT ★★★★★
() автор топика
Ответ на: комментарий от menangen

Надо ведь чтобы был код, который бы в делал нужные преобразования для рантайма. Т.е. нужен некий парсер математических выражений, результат работы которого (например в виде дерева типа https://hsto.org/files/c07/930/b90/c07930b908a14dfd9b71858dfd3810af.png ) скармливался некоей функции, которая бы делала некие преобразования, и потом чтоб еще что-то считать по ней... Как это все впихнуть целиком в компилятор?

SZT ★★★★★
() автор топика
Ответ на: комментарий от menangen

Вообще говоря, компилятор так делать НЕ ДОЛЖЕН. Эти высокоуровневые оптимизации возможны только если компилятор обладает неким «знанием» что вот эти штуки - бигинты, и их можно определенным образом переиначивать. Способов сообщить компилятору об этом - нет

SZT ★★★★★
() автор топика

Чтобы в рантайме преобразовывать, нужно использовать рантаймовые механизмы. Например, вместо expression template сделать expression interface с виртуальными operator+ и прочей ерундой. На «метапрограммирование» такой подход не тянет, конечно.

const86 ★★★★★
()

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

Xintrea ★★★★★
()

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

anonymous
()

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

Forth

monk ★★★★★
()
Ответ на: комментарий от SZT

для флоатов каких-нибудь тоже подойдет, как мне кажется

за давностью лет не скажу за float vs double, но внутри функций типа f(x,y) приходилось делать разные ветки в зависимости от абсолютных и относительных величин x,y - исключительно с целями борьбы с погрешностями.

так что если программер сказал x=a*c+b*c то компилер и обязан вычислять именно в такой последовательности.

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

MKuznetsov ★★★★★
()

Если я захочу в своей программе читать из stdin-а некую формулу, и эту формулу определенным образом преобразовывать, чтобы ее было легче считать, то эти expression templates оказываются совершенно бесполезными

фейспалм

Эх, если был бы какой-нибудь язык программирования без GC

«Если бы губы Никанора Ивановича да приставить к носу Ивана Кузьмича, да взять сколь-нибудь развязанное, какая у Балтазара Балтазаровича, да, пожалуй, прибавить к этому еще дородности Ивана Павловича» (ц)

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

Первое предложение твоего комментария просто дико не соответствует второму +).

Virtuos86 ★★★★★
()

Напиши плагин к clang.

anonymous
()

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

Просто используешь любой math parser для крестов и запиливаешь правила преобразования полученных ast.

Так вот, можно ли как-нибудь сделать это универсально, чтобы и на этапе компиляции, и при выполнении я мог использовать ОДИН КОД для арифметических преобразований?

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

И да в результате ты изобретёшь «заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp» (с)

no-such-file ★★★★★
()

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

https://github.com/eudoxia0/cmacro

используется например вот так

https://github.com/eudoxia0/magma

loz ★★★★★
()
Ответ на: комментарий от no-such-file

Т.е. все эти constexpr-ы, лямбды и прочие нововведения, все это оказалось бесполезным? Короче, в топку эти ваши плюсы. Пойду писать DSL-ы на Scheme генерирующие сишный код. Или еще лучше, сделаю скобочкосинтаксис как в лиспе для Си, и буду в него метапрограммировать через схему.

SZT ★★★★★
() автор топика
Ответ на: комментарий от loz

Ок, посмотрю. Но мне вот интересно, для чего вообще пригодно все то, что комитет стандартизации понадобавлял в последние С++ стандарты? Какая у этих фич область применения?

SZT ★★★★★
() автор топика
Ответ на: комментарий от monk

Forth

Это ж стековый язык. Как там с производительностью? Смогу ли я в нем с той же эффективностью месить память, как это можно делать в С/С++?

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

Чего вы все носитесь с этой производительностью, как с писаной торбой? Медленно? Докупи железа в сервак. Всё равно медленно? Распараллель и докупи ещё серваков.

lazyklimm ★★★★★
()
Ответ на: комментарий от SZT

Как там с производительностью?

В среднем лучше, чем в C++.

Смогу ли я в нем с той же эффективностью месить память, как это можно делать в С/С++?

Конечно. «standard defines ALLOCATE, FREE and RESIZE words that work like malloc(), free(), and realloc() in C».

monk ★★★★★
()
Ответ на: комментарий от SZT

Как там с производительностью

Отлично.

Смогу ли я в нем с той же эффективностью месить память, как это можно делать в С/С++?

Легко.

Это ж стековый язык

А что, кресты передают параметры в функцию не через стек?

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Отлично.
Легко.

Как-то слишком оптимистично. http://dan.corlan.net/bench.html тут тесты показывают, что forth заметно отстает. Надо будет самому протестировать

А что, кресты передают параметры в функцию не через стек?

Сначала регистры, потом стек. Это в винде есть(были?) такие соглашения вызова, когда все только через стек пропихивалось

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

Про плюсы и их комитеты ничего не знаю, это лиспер написал для себя.

loz ★★★★★
()
Ответ на: комментарий от SZT

тут тесты показывают, что forth заметно отстает

Там программа сильно мелкая для теста. Возьми любую с динамическими массивами, списками... В Си у тебя будет много malloc'ов, в Forth в общем случае ссылки на стек. А значит вероятность промаха кэша гораздо меньше.

monk ★★★★★
()
Ответ на: комментарий от monk

Ну в Си я вместо маллоков тоже что-то со стеками могу придумать, например объявить глобальный массив, написать самопальный стековый аллокатор и выделять им память в этом глобальном массиве вместо malloc. Для какого-нибудь контроллера вполне приемлемое решение

SZT ★★★★★
() автор топика

return a*c+b*c; становился return c*(a+b);

Кстати, из коробки такое умеет Pure, но он term rewriting.

mix_mix ★★★★★
()
Ответ на: комментарий от SZT

А если я делаю инженерный калькулятор с ATMEGA16 вместо процессора?

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

anonymous
()
Ответ на: комментарий от anonymous

Я уверен что если калькулятор строит график для множества значений x по формуле y=f(x), лучше будет оптимизировать выражение и считать по нему, чем считать по неоптимизированному выражению. Например, бывают вот такие калькуляторы http://www.amazon.com/Casio-FX-CG10-PRIZM-Graphing-Calculator/dp/B00481K4KS

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

Хорошо. Тогда тебе надо обозреть все реализации интерпретаторов языка на котором у тебя пользователи задают формулы и выбрать тот, в котором реализована подобная оптимизация. При чём тут С++ непонятно.

anonymous
()
Ответ на: комментарий от anonymous

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

SZT ★★★★★
() автор топика
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от SZT

Плюсы тут притом, что в них это нельзя нормально разрулить, или дублирование кода, или кодогенерация

А в каком языке по-другому? eval других языках тоже кодогенерация. REPL для C++: http://sourceforge.net/projects/igcc/

monk ★★★★★
()
Ответ на: комментарий от monk

А в каком языке по-другому? eval других языках тоже кодогенерация.

В других языках может быть некий штатный механизм нормального метапрограммирования. А в плюсах мне надо будет извращаться особым образом: допустим что я на x86-64 компилирую под ARM и у меня есть некий код на плюсах, который мне надо использовать для получения кода на плюсах, который мне надо компилировать в ARМ. Так вот, для этого мне надо на этапе сборки компилировать этот код под x86-64 архитектуру, запускать его... Т.е. одним лишь компилятором в ARM мне не обойтись. К тому же могут появиться проблемы, связанные с разными размерами указателей sizeof(void *) и проч, все это надо учитывать. Получается несколько костыльно.
А вот вариант использовать некий интерпретатор плюсов - интересная идея

SZT ★★★★★
() автор топика
Последнее исправление: SZT (всего исправлений: 2)
Ответ на: комментарий от SZT

допустим что я на x86-64 компилирую под ARM и у меня есть некий код на плюсах, который мне надо использовать для получения кода на плюсах, который мне надо компилировать в ARМ. Так вот, для этого мне надо на этапе сборки компилировать этот код под x86-64 архитектуру, запускать его...

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

monk ★★★★★
()
Ответ на: комментарий от SZT

К тому же могут появиться проблемы, связанные с разными размерами указателей sizeof(void *) и проч, все это надо учитывать.

А это вообще каким боком? Среда метапрограммирования (кодогенерации) и целевой платформы могут вообще не совпадать. Тебе выше ведь привели макросистему на лиспе для C/C++. Вместо лиспа можно было бы использовать и C++.

monk ★★★★★
()
Ответ на: комментарий от monk

Среда метапрограммирования (кодогенерации) и целевой платформы могут вообще не совпадать.

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

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

хочется писать код один раз на одном языке программирования

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

Примеры для С/C++ - flex/bison и embed SQL.

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

А почему плюсостандартизаторы не хотят добавить ШТАТНУЮ возможность расширять синтаксис? Может мне еще процитировать кое-какие фрагменты из фундаментального труда «Lisp: побеждая посредственность» про возможность расширения языка средствами самого языка? Например:

Но, скажем, если язык A поддерживает рекурсию, а B — нет, это нечто, что нельзя исправить написанием библиотечных функций.
...
Дело не в том, что в Lisp'е странный синтаксис, скорее, его нет вообще. Программы пишутся в готовых синтаксических деревьях, которые в других языках генерируются парсером во время разбора исходного текста. Эти синтаксические деревья в Lisp'е полностью доступны вашим программам, и вы можете писать программы, которые изменяют эти деревья. В Lisp'е подобные программы называются макросы. Это программы, которые пишут программы.

SZT ★★★★★
() автор топика
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от SZT

А почему плюсостандартизаторы не хотят добавить ШТАТНУЮ возможность расширять синтаксис?

Потому что тогда существующий синтаксис придётся превратить в Lisp.

Вот в Scala, где синтаксис похож на С++, добавили макросы. Так это выглядит так:

trait DebugMacros {
  def debug(params: Any*): Unit = macro DebugMacros.debug_impl
}
object DebugMacros extends DebugMacros {
def debug_impl(c: Context)(params: c.Expr[Any]*): c.Expr[Unit] = {
    import c.universe._

    val trees = params.map { param =>
      param.tree match {
        // Keeping constants as-is
        // The c.universe prefixes aren't necessary, but otherwise Idea keeps importing weird stuff ...
        case c.universe.Literal(c.universe.Constant(const)) => {
          val reified = reify { print(param.splice) }
          reified.tree
        }
        case _ => {
          val paramRep = show(param.tree)
          val paramRepTree = Literal(Constant(paramRep))
          val paramRepExpr = c.Expr[String](paramRepTree)
          val reified = reify { print(paramRepExpr.splice + " = " + param.splice) }
          reified.tree
        }
      }
    }
// Inserting ", " between trees, and a println at the end.
    val separators = (1 to trees.size-1).map(_ => (reify { print(", ") }).tree) :+ (reify { println() }).tree
    val treesWithSeparators = trees.zip(separators).flatMap(p => List(p._1, p._2))

    c.Expr[Unit](Block(treesWithSeparators.toList, Literal(Constant(()))))
  }
}

Можешь сравнить с лисповским полным аналогом

(defmacro debug (&rest args)
  `(format t "~{~a~^, ~}"
       (list ,@(mapcar
          (lambda (x)
            (if (constantp x) x `(format nil "~s = ~s" ',x  ,x)))
          args))))

monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.