For kernel mode code, if I have choice between using a #define or a FORCEINLINE function, the FORCEINLINE function wins every time. #defines have their place, especially for quotifying (the # operator) or concatenating (the ## operator), but they have no place in my heart for constants or pseudo functions.
FORCEINLINEs have type checking. On a debug build, they show up in the symbol information. They do not have weird side affects due to replacement by expression (such as invoking the #define max(a++, b++) would have). In KMDF, we use FORCEINLINE functions for all DDI calls and all of our structure init functions. The type checking alone is enough for me; it allows for more maintainable and correct code.
I am also intentionally leaving templates out of this for a reason. C++ is not supported in the kernel. For those who use C++, you are cautioned to use a safe subset of it. Templates are far from a safe subset because there is no way to deterministically control into which section the code will be linked into. This is important because you can accidentally have your template generated code placed into a PAGEable section, but the code cannot be PAGEable (and you BSOD very quickly 🙁 ).