Open kainjow opened 9 years ago
Sounds like a useful and interesting challange. The tricky bits would be (1) gracefully degrading for compliers without constexpr
support, and for dynamic format strings, and (2) implementing it all without bloating up the codebase too much more. The first link gives me hope that it's actually quite simple.
I had a look at this. It's fairly easy (if cumbersome) to implement a constexpr function which counts the number of format specs in a string at compile time:
constexpr int countFormatSpecs(const char* str, int i, int N)
{
return
/*if*/ (i >= N-1) ? (
/*if*/ (str[i] != '%') ?
0
/*else*/ :
throw std::logic_error("Format string prematurely terminated with a '%'")
)
/*else*/ : (
/*if*/ (str[i] == '%') ? (
/*if*/ (str[i+1] == '%') ? (
countFormatSpecs(str, i+2, N)
)
/*else*/ : (
1 + countFormatSpecs(str, i+1, N)
)
)
/*else*/ : (
countFormatSpecs(str, i+1, N)
)
)
;
}
template<int N>
constexpr int countFormatSpecs(const char (&str)[N])
{
return countFormatSpecs(str, 0, N-1);
}
Unfortunately this was about as far as I got. Ideally, we'd have the following function overloads:
// Usual version, with runtime format string
template<typename... Args>
void printf(const char* fmt, const Args&... args)
{
// ...
}
// Version for compile time checking of format string (invalid C++)
template<int N, typename... Args>
void printf(constexpr char (&fmt)[N], const Args&... args)
{
static_assert(countFormatSpecs(fmt) == sizeof...(args));
// ...
}
However, there's no such thing as a constexpr
function argument, so the second function here can't be written in this form. In fact, I can't see any way to write it while preserving the usual syntax: printf()
itself is clearly not a constexpr function, so any compile time checking would presumably have to be done outside. However, it's only inside printf()
that we know the number of arguments passed. The only way I can see to combine these things is in the type signature, which at a quick glance is what the author of safe_printf
seems to have done. This is clever, but the unfortunately ugly and non-printf compatible syntax which results is a dealbreaker for tinyformat.
Having said that, it might still be possible to achieve a syntax like
printf(FMT("foo %d"), 100);
which could indeed be useful if you were using tinyformat via a logging macro. Of course, FMT
would have to be something longer in practice, at least by default.
In all this, it's worth keeping in mind that constexpr
functions have the interesting property that they must be callable at runtime as well as compile time. This means none of the usual compile time template tricks work on the arguments of constexpr function since those arguments themselves aren't constexpr variables.
Here's an amusing thought... we could likely implement this with nice syntax if we were willing to use macros :grimacing:
For example
format_("foo %d", 100);
// expand to something like
format(check_format("foo %d", 100), 100);
wherecheck_format
returns "foo %d"
, or produces a compile time error if the arguments don't match.
With constexpr it seems to be possible to parse the format string at compile time to determine if the number of arguments passed actually matches the number of arguments specified.
This could fill the void when moving from built-in printfs that have compile support (via attribute).
Links: http://akrzemi1.wordpress.com/2011/05/11/parsing-strings-at-compile-time-part-i/ http://abel.web.elte.hu/mpllibs/safe_printf/