Встроенные функции, как видно из названия, ограничены функциональными задачами, выполнением некоторого кода.
Макросы имеют гораздо более широкое приложение, которое они могут расширить, например, до объявлений или заменить целые языковые конструкции. Некоторые примеры (написанные для C и C ++), которые нельзя сделать с помощью функций:
typedef struct POD { double a; unsigned b } POD;
#declare POD_INITIALIZER { 1.0, 37u }
POD myPOD = POD_INITIALIZER;
#define DIFFICULT_CASE(X) case (X)+2 :; case (X)+3
#define EASY_CASE(X) case (X)+4 :; case (X)+5
switch (a) {
default: ++a; break;
EASY_CASE('0'): --a; break;
DIFFICULT_CASE('A'): a = helperfunction(a); break;
}
#define PRINT_VALUE(X) \
do { \
char const* _form = #X " has value 0x%lx\n"; \
fprintf(stderr, _form, (unsigned long)(X)); \
} while (false)
В контексте C ++ в Boost есть много других примеров, которые более сложны и полезны.
Но поскольку с такими макросами вы каким-то образом расширяете язык (не обязательно, препроцессор является его частью), многие люди не любят макросы, особенно в сообществе C ++, чуть меньше в сообществе C.
В любом случае, если вы используете такие конструкции, вы всегда должны четко понимать, чего следует достичь, хорошо документировать и бороться с искушением запутать ваш код.