Шаблоны vs макросы препроцессора

Вероятно, может возникнуть вопрос, нельзя ли для целей кодогенерации использовать макроподстановки? В принципе, действительно, можно. Например, тот же набор функций Max легко представить в виде макроса:

#define MAX(V1,V2) ((V1) > (V2) ? (V1) : (V2))

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

При сравнении макросов и шаблонов следует отметить следующие различия.

Макросы "раскрываются" и подменяются в исходном тексте препроцессором, до начала компиляции. При этом отсутствует информация о типах параметров и контексте, в котором подставляется содержимое макроса. В частности, макрос MAX не может обеспечить проверку того, что типы параметров V1 и V2 совпадают, а также то, что для них определен оператор сравнения '>'. Кроме того, если в тексте программы повстречается переменная с именем MAX, препроцессор попытается подставить на её место "вызов" макроса MAX и будет "недоволен" отсутствием аргументов. Что еще более страшно, подобные подстановки игнорируют, в каких пространствах имен или классах, повстречался токен MAX — для них подойдет любой.

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

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

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

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

В макросах возможны побочные эффекты, которые мы рассматривали в разделе Форма #define в виде псевдо-функции: если аргументами макроса MAX будут выражения с инкрементами/декрементами, то они выполнятся два раза.

Вместе с тем, у макросов есть и кое-какие преимущества. Макросы способны генерировать любой текст, а не только правильные языковые конструкции. Например, с помощью нескольких макросов можно сымитировать инструкцию switch для строк (хотя такой подход и не рекомендуется).

В стандартной библиотеке макросы используются, в частности, для организации обработки событий на графиках (см. MQL5/Include/Controls/Defines.mqh: EVENT_MAP_BEGIN, EVENT_MAP_END, ON_EVENT и т.д.). Сделать так на шаблонах не получится, но способ компоновки карты событий на макросах, разумеется, далеко не единственный и не самый удобный для отладки. Отлаживать пошаговое (построчное) исполнение кода в макросах затруднительно. Шаблоны же поддерживают отладку в полном объеме.