Type safe variadic printf
stranger.systems
stranger.systems
But it's really not fair to C to exclude all the special compiler support we do have with -Werror=format=2. Given the existence of the format/format_arg attributes it's really quite powerful; the only thing that's missing is support for custom formats, which requires a compiler plugin.
In C++ you can even make a wrapper macro that safely converts arbitrary C++ classes into suitable types for C printf. If you know all your types ahead of time you can do similar with C11 _Generic I think.
For a lower-tech approach, you can even do one in Standard ML[1,2], more or less in plain Hindley–Milner (no substantial use of modules). The error messages are admittedly miserable. Perhaps it would be more correct to call that one a type-safe middle ground between printf and std::ostream::operaror<<, though, and I have to admit that loses what for me is one of the principal advantages of (POSIX) printf: the format string is a string, so localization—which inevitably involves rearranging placeholders—is much easier.
It might be that the correct solution involves a string with placeholders, but with formatting instructions—and all dependencies on argument types—pushed outside (to the argument list or similar). I’ve toyed with something like that in C; unfortunately, it’s not as succint as normal printf unless I want to tie it to dynamic memory allocation. For C, there’s also the advantage that you can avoid pulling in floating-point formatting code if you don’t need it, without resorting to GCC-specific tricks like Cosmopolitan does[3], and that code is the biggest (code and data) space hog that keeps printf out of embedded and other kinds of lean programs.
The type should not be coming from the format string but be inferred from the arguments that are being combined with that string. Of course certain conversion specifiers in the format string only makes sense for numbers.
And as much as named arguments are nice from a user perspective, they do have significant runtime overhead if the format string can be localized.