For others who stumble across this post, the modern standardized way to do this beyond C++20 and C23 is by using __VA_OPT__(content).
When variadic arguments are passed and thus __VA_ARGS__ is defined __VA_OPT__(content) is replaced with content.
e.g.
#define custom_printf(format, ...) printf(format __VA_OPT__(,) __VA_ARGS__)
C23: https://en.cppreference.com/w/c/preprocessor/replace.html
C++20: https://en.cppreference.com/w/cpp/preprocessor/replace.html