Я вот задумался. Взять например функцию qsort() из стандартной библиотеки сей, ну которая принимает указатель на массив, количество элементов в этом самом массиве, размер одного элемента массива и функцию которая принимает два указателя на элементы массива, делает сравнение и возвращает int. Что если реализовать механизм встраивания всей этой фигни (за исключением разве что указателя на тот самый массив, который сортировать надо) и можно значительно ускорить процесс сортировки. Например, тут https://books.google.com/books?id=RPnWe6QKnCcC&pg=PA201&lpg=PA201&... говорят что std::sort быстрее qsort т.к. там компилятор может заинлайнить это. Но это не универсально. Если в компилтайме нам например неизвестен способ сравнивания двух элементов массива(т.е. мы это узнаем только на этапе выполнения), там ничего не заинлайнится. Например, как решать такую задачу : программа читает некий массив чисел(неизвестно заранее сколько, допустим читаем из файла. Пусть это будет тип float), пользователь вводит некую формулу, например y=x*x+2*(x-1) и каждое число из списка подставляются последовательно в эту формулу как x, вычисляется y и записывается потом куда-то в файл. Самый простой способ реализовать подобное на C - генерить в рантайме на основе введенной формулы некий исходник на C, компилировать его как .so, подгружать через dlopen и запустить. Желательно при этом, чтобы сам цикл с последовательным применением формулы был тоже сгенерирован внутри этой самой библиотеки, чтобы не было проседаний на каждый вызов функции. Шаблоны из плюсов тут ничем не помогут в плане оптимизации, они не могут отработать в процессе выполнения кода и что-либо там менять-добавлять. А через inline callback-функции это было бы очень просто сделать без особых проблем. Например, мы получаем от пользователя нуль-терминированную строку вида «x*x+2*(x-1)». Значит нам нужна функция, которая бы принимала указатель на строку, указатель на массив-источник, указатель на массив-назначение и кол-во элементов в массиве, которое надо по этой формуле преобразовать. Назовем эту функцию apply_ar. Получим что-то вроде
void (*apply_arr(char *str, float *src, float *dst, size_t nmemb))(void)
{
какой-то_код;
return somestuff;
}
Так оно просто вернет указатель на функцию, которая вообще ничего не принимает т.е. просто встроит в функцию все аргументы. Будет в итоге сгенерирована функция, которая по неким вшитым в код адресам в цикле читает флоаты, применяет вшитую в код формулу и записывает результат в заранее оговоренную область памяти. Получилось очень неуниверсально. Задачу можно изменить, например может потребоваться обрабатывать много массивов разной длины с разными адресами *src *dst но по одинаковой формуле. Тогда надо городить другую функцию, на этот раз принимающую только строку, описывающую формулу преобразования и которая возвращает функцию, принимающую указатель на источник, назначение, количество элементов. Примерно так:
void (*apply_arr(char *str))(float *src, float *dst, size_t nmemb)
{
какой-то_код;
return somestuff;
}
Получается что-то вроде каррирования с суперкомпиляцией, специализация программ методом частичных вычислений:
http://keldysh.ru/papers/2008/prep12/prep2008_12.html#_Toc193602742А теперь насчет callback-ов и их встраивания. Можно передавать в нашу функцию еще и код, который бы делал само вычисление по формуле. Для этого можно сделать функцию, в которую если передать например строку вида «x*x+2*(x-1)» вернет указатель на функцию вида
float calculate(float s)
{
return x*x+2*(x-1);
}
И результат этой функции передавать в функцию apply_arr. Таким образом, если кто-нибудь захочет задавать формулу например в обратной польской записи, то мы просто напишем еще одну функцию и в случае чего будем передавать ее результат. Только тут просто указатель на функцию передать в рантайме не выйдет (нам ведь надо чтобы оно инлайнилось в функцию, которая будет сгенерирована функцией), таким образом нужно вводить некий байткод, промежуточное представление, которое потом бы компилировалось. Можно взять готовое, например формат промежуточного представления от какого-нибудь компилятора. В самой нашей функции apply_arr тогда тоже надо хранить этот байткод, и наша функция должна два эти байткода (свой и тот который в нее передается) уметь комбинировать и выдавать на выходе указатель на функцию с нативным кодом. Что самое интересное, если это все как следует продумать, можно сделать полноценный лисп поверх C, только с ручным управлением памятью. Самое замечательное, что это хоть сейчас можно в какой-то степени реализовать, только вместо байткода надо брать обычный сишный код, а для «встраивания» кода внутрь кода использовать что-то вроде
printf("some code;\n"
"%s\n"
"some code;\n");
, потом кормить этим компилятор и загружать получившийся код в исполняемую область памяти. Для более нормальной реализации всего этого, надо каким-то образом переделать компилятор. Саму специализацию можно делать как на этапе компиляции, так и в рантайме. Если промежуточное представление сделать как AST, это будет вообще замечательно. Всю программу можно собрать из этих коллбеков, и не будет ни одной нормальной привычной функции. Вся программа это функция, в которую передается массив из функций, каждая из функций может быть например оператором сложения int с int, float с float, разыменование указателя, операцией приведения типа, вызов какой-то функции. При том, необязательно все это дело инлайнить. Сами функции могут просто вызываться обычным путем, несмотря на возможность их заинлайнить. Таким образом можно переопределить любое действие, при том в рантайме (например можно сделать, что при попытке записи в или чтения из памяти по такому-то адресу, это событие логировалось. Или смотреть статистику, какие функции с какими аргументами вызываются)
Так вот, вопрос: какие могут быть сложности в реализации всего вышеописанного? Имеет ли смысл создавать такую систему метапрограммирования поверх C (если нет, то поверх чего)? В каких-нибудь языках программирования без сборки мусора, с ручным управлением памятью и с возможностью компилировать в нативный код (без всяких там интерпретаторов) уже реализовано подобное? Желательна так же возможность прямой работы с памятью, с сырыми указателями. Вообще, все это напоминает какой-то лисп, но лисп мне не подходит по вышеобозначенным критериям.