Возможности метапрограммирования в си не так уж велики, они уступают таковым во многих языках программирования, включая C++, но тем не менее они позволяют сделать многое. Правда, следует сказать, многие программисты об этом многом и не догадываются.
Основных возможности метапрограммирования в си всего две:
Препроцессор, конечно же, самая известная и наиболее популярная возможность метапрограммирования, но вычисления времени компиляции тоже не стоит сбрасывать со счетов — они предоставляют порой весьма интересные возможности.
Следует заметить, эти две возможности чётко разделены, и «не смешиваются». Так, препроцессор воспринимает текст просто как последовательность символов, и не понимает синтаксических конструкций языка, а вычисления производятся уже на этапе синтаксического анализа. Это важно понимать, так как возможностями вычислений времени компиляции (такими, как использование sizeof) не удастся воспользоваться в препроцессоре, и наоборот, вычисления будут производиться уже над конечным результатом препроцессора.
Основная «движущая сила» препроцессора в плане метапрограммирования — это, конечно же, макроподстановщик. Я думаю, многие с возможностями подстановщика хорошо знакомы, приведу лишь их список
#define FOO bar // простая подстановка #define BAR(x) foo(x+1) // подстановка с параметрами #define CAT(x, y) x##y // конкатенация токенов #define STR(x) #x // преобразование в строку
Все эти конструкции хорошо описаны в стандарте, поэтому заново объяснять не буду. Чтобы глубоко понимать работу этих вещей, следует обратить особое внимание на такие детали, как порядок действий — подстановка параметров, преобразование в строку, конактенация, подстановка макросов. Иначе смысл таких вещей (вот хрестоматийный пример) будет непонятен:
#define CAT(x, y) XCAT(x, y) #define XCAT(x, y) x##y
Скажу лишь, что в данном случае так сделано, чтобы перед конкатенцией совершалась макроподстановка, так как если сделать в один макрос, она не будет происходить.
Из побочных возможностей следует обязательно упомянуть — включение других файлов (при мудром использовании может тоже многое сделать), и условное исполнение. Что интересно, включение других файлов позволяет использовать макросы, и этой возможность можно использовать весьма интересно.
Условные операторы, в свою очередь, обладают интересной чертой — они производят самые настоящие арифметические и логические вычисления, но это не стоит путать с вычислениями во время компиляции — это совсем другая вещь.