Las funciones en línea son, como su nombre indica, restringidas a tareas funcionales, la ejecución de algún código.
Las macros tienen una aplicación mucho más amplia que pueden expandir, por ejemplo, a declaraciones o reemplazar constructos de lenguaje completos. Algunos ejemplos (escritas para C y C++) que no se pueden hacer con las funciones de:
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)
En el contexto de C++, Boost tiene mucha más ejemplos que están más involucrados y más útil.
Pero debido a que tales macros están ampliando el lenguaje (no estrictamente, el preprocesador es parte de él) a muchas personas no les gustan las macros, particularmente en la comunidad C++, un poco menos en la comunidad C.
En cualquier caso, si utiliza tales construcciones, siempre debe tener muy claro lo que debe lograr, documentar bien y luchar contra la tentación de ofuscar su código.
Conocer automáticamente el nombre del archivo y el número de línea es otro truco útil que solo es posible con las macros. –