Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse format string at compile time with constexpr to determine number of arguments #19

Open
kainjow opened this issue Oct 2, 2014 · 3 comments

Comments

@kainjow
Copy link
Contributor

kainjow commented Oct 2, 2014

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/

@c42f
Copy link
Owner

c42f commented Oct 2, 2014

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.

@c42f
Copy link
Owner

c42f commented Oct 12, 2014

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.

@c42f
Copy link
Owner

c42f commented Dec 15, 2019

Here's an amusing thought... we could likely implement this with nice syntax if we were willing to use macros 😬

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants