Комментарии 13
Мсье знает толк…
+29
Вы чёкнутый :)
+2
Если бы в С+ сделали нормальную «интерпретацию во время компиляции» (известную также как синтаксические макросы), было бы очень хорошо. А этот пример (как и весь Boost) лишний раз показывает, что бывает, когда в языке чего-то изначально не предусмотрено, а программистам очень хочется.
+1
Интерпретация во время компиляции очень хорошо сделана в языке D. С другой стороны, есть инструментарий (тот же ReSharper) который умеет делать интерпретацию во время написания кода (не во время компиляции!) и тем самым у вас появляется возможность использовать шаблоны и потом их «инлайнить» в код. Только проблема в том что если что-то изменится, придется переписывать все снова.
+4
Мне понравилось как синтаксические макросы устроены в Nemerle. А про D очень мало информации (в книге Александреску этого нет, на официальном сайте простейший пример). Конструкция mixin — там просто строки, в которых код. Непонятно, можно ли эти строки конструировать программно, если можно — какие операции разрешены для времени компиляции и т.д. И в целом как-то бессистемно все… может вы расскажете?
0
А про D очень мало информации (в книге Александреску этого нет, на официальном сайте простейший пример). Конструкция mixin — там просто строки, в которых код. Непонятно, можно ли эти строки конструировать программно, если можно — какие операции разрешены для времени компиляции и т.д.
Можно, причем двумя разными способами. Первый — выполнение функций в compile time, второй — через шаблоны. Для примера рассмотрю первый вариант. Практически любые функции, которые не используют IO, указатели и другие низкоуровневые фичи языка можно выполнять на этапе компиляции. Для этого нужно просто использовать функцию в контексте, в котором у нее нет выбора, кроме как выполниться при компиляции (например, присвоить результат к константе enum).
Иллюстрация: есть два энума A и B, нужно сгененрировать switch по элементам первого энума, который поставит в соответствие каждому элементу A элемент в B.
import std.stdio;
enum A
{
a1,
a2,
a3
}
enum B
{
b1,
b2,
b3
}
// Берем два энума и название переменной, для которой будет
// сгенерирован switch
string genSwitch(E1, E2)(string var)
if(is(E1 == enum) && is(E2 == enum))
{
// Просим компилятор выдать нам tuple всех элементов типа
enum E1Members = __traits(allMembers, E1);
enum E2Members = __traits(allMembers, E2);
// Проверяем, чтобы их длинны совпадали, проверка во время компиляции
static assert(E1Members.length == E2Members.length);
// Используем все обычные средства для работы со строками
// для генерации свитча
string s = "final switch("~var~")\n{\n";
foreach(i, member; E1Members)
{
s ~= "\tcase("~E1.stringof~"."~member~"):\n\t{\n";
s ~= "\t\twriteln("~E2.stringof~"."~E2Members[i]~");\n";
s ~= "\t\tbreak;\n\t}\n";
}
return s~"}";
}
void main()
{
auto a = A.a2;
// Способ вывести что-нибудь во время компиляции в консоль
pragma(msg, genSwitch!(A, B)("a"));
// Встраиваем сгенеренный свитч
mixin(genSwitch!(A, B)("a"));
}
Во время компиляции увидим:
final switch(a)
{
case(A.a1):
{
writeln(B.b1);
break;
}
case(A.a2):
{
writeln(B.b2);
break;
}
case(A.a3):
{
writeln(B.b3);
break;
}
}
P.S. Это просто иллюстрация. Генерацию через шаблоны того же кода, если интересно, то тоже приведу.
+6
Приводите конечно.
0
Все же шаблоны больше подходят для операций над типами, проверки их свойств и т.п. Но для генерации кода тоже подходят:
// Через variable length arguments можно в шаблон
// передавать практически что угодно
template GenSwitch(E1, E2, TS...)
{
// Но лучше проверять, что именно пользователь передал
static assert(TS.length == 1);
static assert(is(typeof(TS[0]) == string));
enum var = TS[0];
enum E1Members = __traits(allMembers, E1);
enum E2Members = __traits(allMembers, E2);
static assert(E1Members.length == E2Members.length);
// Циклы заменяем рекурсией через nested templates
private template GenBody(TSS...)
{
// через TSS передаем текущий номер итерации
enum i = TSS[0];
// и оставшийся кусок от E1Members
alias TS = TSS[1..$];
static if(TS.length == 0) // Дно рекурсии
{
// Процедура 'возврата значения/типа' из шаблона
// нужно объявить enum или alias,
// имя которого совпадает с именем шаблона
enum GenBody = "";
}
else
{
enum GenBody = "\tcase("~E1.stringof~"."~TS[0]~"):\n\t{\n"
~ "\t\twriteln("~E2.stringof~"."~E2Members[i]~");\n"
~ "\t\tbreak;\n\t}\n"
~ GenBody!(i+1, TS[1..$]);
}
}
enum GenSwitch = "final switch("~var~")\n{\n"
~GenBody!(0, E1Members)
~"}";
}
+1
В книжке Александреску делается акцент на том, что строки можно генерировать в compile-time, еще он в качестве примера приводит возможность написания compile-time DSL на D.
+1
Смотрю на Smalltalk, древний как… в общем очень древний, где минимальной единицей компиляции является не модуль/программа, а метод, и душа радуется — чтобы произвести какие-то вычисления во время компиляции, пишете например ##(65535 factorial) — и получите почти килобайтный экземпляр BigInteger, вкомпилённый прямо в байт-код метода. Я это привёл просто к примеру, здесь не про «нафига это надо», а «смотрите как МОЩНО умеет!».
Не говоря уже о том, что синтаксис в скобках ничем не отличается от «обычного». Ну и само собой, классы и объекты, над которыми производим вычисления (посылка им сообщений), должны существовать во время компиляции.
Алан Кей поддел в своё время C++ за кой-какие его «шероховатости», и видимо не зря :)
Не говоря уже о том, что синтаксис в скобках ничем не отличается от «обычного». Ну и само собой, классы и объекты, над которыми производим вычисления (посылка им сообщений), должны существовать во время компиляции.
Алан Кей поддел в своё время C++ за кой-какие его «шероховатости», и видимо не зря :)
+7
Всё время, пока я писал пост, меня не покидала мысль: «Это или уже есть в Бусте, или пишется там в три строки».
Эта мысль преследует меня постоянно, когда нет возможности использовать Boost
+2
+1
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Интерпретация во время компиляции, или Альтернативное понимание лямбд в C++11