## Compile time checking of printf-like format strings

Inspired by [this open ticket](https://svn.boost.org/trac/boost/ticket/6815) on boost, this seeks to complete the work there

Given a printf-style format string and associated arguments, a `static_assert` is performed on whether the format string and arguments are valid.

I'm particularly interested in:

- Have I covered all possible format strings?
- Am I doing this in the most efficient way?

### Code:

This includes changes based on the comments from [@Loki Astari](http://codereview.stackexchange.com/users/507/loki-astari) on the previous iteration of this code [here](http://codereview.stackexchange.com/a/84795/47528)

[Here is the below code running on ideone](https://ideone.com/EzstNy)

    #include <cstddef>
    #include <cstdio>
    #include <stdexcept>
    #include <boost/utility/string_ref.hpp>
    #include <boost/format.hpp>
    
    #ifndef BOOST_PP_VARIADICS
    #    define BOOST_PP_VARIADICS
    #endif
    #include <boost/preprocessor.hpp>
    
    template<typename... Ts>
    struct Format
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n);
    };
    
    //////////////////////
    
    template<std::size_t N>
    constexpr bool checkValidFormats(const char (&fmt)[N], size_t n, char c)
    {
        return n >= N ?
                throw std::logic_error("invalid format for type")
            : fmt[n] == c ?
                true
            : checkValidFormats(fmt, n + 1, c);
    }
    
    template<class>
    struct Type;
    
    #define SUPPORTED_TYPE(T, Fmts) \
    template<> \
    struct Type<T> \
    { \
        template<std::size_t N> \
        constexpr static bool check(const char (&fmt)[N], std::size_t n) \
        { \
            return n >= N ? \
                    throw std::logic_error("invalid format for type") \
                : checkValidFormats(Fmts, 0, fmt[n]); \
        } \
    }
    
    SUPPORTED_TYPE(char,              "c");
    SUPPORTED_TYPE(int8_t,            "cd");
    SUPPORTED_TYPE(uint8_t,           "cu");
    SUPPORTED_TYPE(int16_t,           "d");
    SUPPORTED_TYPE(uint16_t,          "u");
    SUPPORTED_TYPE(int32_t,           "d");
    SUPPORTED_TYPE(uint32_t,          "u");
    SUPPORTED_TYPE(char*,             "s");
    SUPPORTED_TYPE(unsigned char*,    "s");
    SUPPORTED_TYPE(const char*,       "s");
    SUPPORTED_TYPE(std::string,       "s");
    SUPPORTED_TYPE(boost::string_ref, "s");
    SUPPORTED_TYPE(double,            "f");
    SUPPORTED_TYPE(float,             "f");
    
    #define SUPPORTED_LL_TYPE(T, C) \
    template<> \
    struct Type<T> \
    { \
        template<std::size_t N> \
        static constexpr bool check(const char (&fmt)[N], std::size_t n) \
        { \
            return n < N && \
                   n - 2 >= 0 && \
                   fmt[n]     == C   && \
                   fmt[n - 1] == 'l' && \
                   fmt[n - 2] == 'l' ? \
                        true \
                : throw std::logic_error("invalid format for type"); \
        } \
    }
    
    SUPPORTED_LL_TYPE(int64_t,  'd');
    SUPPORTED_LL_TYPE(uint64_t, 'u');
    
    template<typename... Ts>
    struct Argument
    {
        template<std::size_t N>
        static constexpr bool check(const char (&)[N], std::size_t)
        {
            return false;
        }
    };
    
    template<typename T, typename... Ts>
    struct Argument<T, Ts...>
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n)
        {
            //    %[<flags>][<width>][.<precision>][<length>]<specifier>
            //        specifier := d|i|u|o|x|X|f|F|e|E|g|G|a|A|c|s|p|n
            return Type< typename std::decay<T>::type>::check(fmt, n) &&
                    Format<Ts...>::check(fmt, n + 1);
        }
    };
    
    ///////////////////////////
    
    template<size_t N>
    constexpr bool isDoubleLengthSpecifier(const char (&fmt)[N], std::size_t n)
    {
        // hh | ll
        return n + 2 < N &&
               ((fmt[n] == 'h' && fmt[n + 1] == 'h') ||
                (fmt[n] == 'l' && fmt[n + 1] == 'l'));
    }
    
    template<size_t N>
    constexpr bool isSingleLengthSpecifier(const char (&fmt)[N], std::size_t n)
    {
        // h | l | j | z | t | L
        return n + 1 < N &&
               (fmt[n] == 'h' ||
                fmt[n] == 'l' ||
                fmt[n] == 'j' ||
                fmt[n] == 'z' ||
                fmt[n] == 't' ||
                fmt[n] == 'L');
    }
    
    template<size_t N>
    constexpr size_t nextNonLengthSpecifier(const char (&fmt)[N], std::size_t n)
    {
        return
                isDoubleLengthSpecifier(fmt, n) ? n + 2
              : isSingleLengthSpecifier(fmt, n) ? n + 1
              : n;
    }
    
    template<typename... Ts>
    struct Length
    {
        template<std::size_t N>
        static constexpr bool check(const char (&)[N], std::size_t)
        {
            return false;
        }
    };
    
    template<typename T, typename... Ts>
    struct Length<T, Ts...>
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n)
        {
            //    %[<flags>][<width>][.<precision>][<length>]<specifier>
            //        length    := hh|h|l|ll|j|z|t|L
            return Argument<T, Ts...>::check(fmt, nextNonLengthSpecifier(fmt, n));
        }
    };
    
    ///////////////////////////
    
    template<std::size_t N>
    constexpr size_t nextNonLiteralPrecision(const char (&fmt)[N], std::size_t n)
    {
        return
            n >= N ?
                throw std::logic_error("invalid format string - parsing precision")
            : fmt[n] >= '0' && fmt[n] <= '9' ?
                    nextNonLiteralPrecision(fmt, n + 1)
            : n;
    }
    
    template<typename... Ts>
    struct Precision
    {
        template<std::size_t N>
        static constexpr bool check(const char (&)[N], std::size_t)
        {
            return false;
        }
    };
    
    template<typename T, typename... Ts>
    struct Precision<T, Ts...>
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n)
        {
            //    %[<flags>][<width>][.<precision>][<length>]<specifier>
            //        precision := <number>|'*'        // A number or a '*'
    
            // if precision is a provided argument, validate it is integral
            return n + 1 < N && fmt[n] == '.' && fmt[n + 1] == '*' ?
                    std::is_integral<T>::value && Length<Ts...>::check(fmt, n + 2)
    
            // otherwise skip over any literal precision
            : n + 1 < N && fmt[n] == '.' ?
                    Length<T, Ts...>::check(fmt, nextNonLiteralPrecision(fmt, n + 1))
    
            : Length<T, Ts...>::check(fmt, n);
        }
    };
    
    ///////////////////////////
    
    template<std::size_t N>
    constexpr size_t nextNonLiteralWidth(const char (&fmt)[N], std::size_t n)
    {
        return
            n >= N ?
                throw std::logic_error("invalid format string - parsing width")
            : fmt[n] >= '0' && fmt[n] <= '9' ?
                nextNonLiteralWidth(fmt, n + 1)
            : n;
    }
    
    template<typename... Ts>
    struct Width
    {
        template<std::size_t N>
        static constexpr bool check(const char (&)[N], std::size_t)
        {
            return false;
        }
    };
    
    template<typename T, typename... Ts>
    struct Width<T, Ts...>
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n)
        {
            //    %[<flags>][<width>][.<precision>][<length>]<specifier>
            //        width     := <number>|'*'        // A number or a '*'
    
            // if width is a provided argument, validate it is integral
            return fmt[n] == '*' ?
                    std::is_integral<T>::value && Precision<Ts...>::check(fmt, n + 1)
    
            // otherwise skip over any literal width
            : Precision<T, Ts...>::check(fmt, nextNonLiteralWidth(fmt, n));
        }
    };
    
    ///////////////////////////
    
    template<size_t N>
    constexpr bool isFlag(const char (&fmt)[N], std::size_t n)
    {
        return n + 1 < N &&
              (fmt[n] == '-' ||
               fmt[n] == '+' ||
               fmt[n] == ' ' ||
               fmt[n] == '#' ||
               fmt[n] == '0');
    }
    
    template<std::size_t N>
    constexpr size_t nextNonFlag(const char (&fmt)[N], std::size_t n)
    {
        return
            n >= N ?
                throw std::logic_error("invalid format string")
            : isFlag(fmt, n) ?
                nextNonFlag(fmt, n + 1)
            : n;
    }
    
    template<typename T, typename... Ts>
    struct Flags
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n)
        {
            //    %[<flags>][<width>][.<precision>][<length>]<specifier>
            //        flags     := [-+ #0]*            // Zero or more
            return Width<T, Ts...>::check(fmt, nextNonFlag(fmt, n));
        }
    };
    
    ///////////////////////////
    
    template<size_t N>
    constexpr bool isLiteralPercent(const char (&fmt)[N], std::size_t n)
    {
        return n + 1 <= N && fmt[n] == '%' && fmt[n + 1] == '%';
    }
    
    template<typename T, typename... Ts>
    struct Format<T, Ts...>
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n)
        {
            return
                n >= N ?
                    throw std::logic_error("too many arguments for provided format string")
    
                // skip non-format specifiers (ie: not a % character)
                : fmt[n] != '%' ?
                    Format<T, Ts...>::check(fmt, n + 1)
    
                // %%
                : isLiteralPercent(fmt, n) ?
                    Format<T, Ts...>::check(fmt, n + 2)
    
                // we've found a format specifier
                : Flags<T, Ts...>::check(fmt, n + 1);
        }
    };
    
    template<>
    struct Format<>
    {
        template<std::size_t N>
        static constexpr bool check(const char (&fmt)[N], std::size_t n)
        {
            return
                n>= N ?
                    true
                : fmt[n] != '%' ?
                    check(fmt, n + 1)
                : fmt[n + 1] == '%' ?
                    check(fmt, n + 2)
                : throw std::logic_error("too few arguments for provided format string");
        }
    };
    
    ////////////////
    
    // printing...
    
    void add(boost::format&)
    { }
    
    template<typename T, typename... Ts>
    void add(boost::format& f, const T& arg, const Ts&... ts)
    {
        f % arg;
        add(f, ts...);
    }
    
    ////////////////
    
    #define PP_PARENTHESISE_WITH_TOKEN(r, token, i, e) \
        BOOST_PP_COMMA_IF(i) token(e)
    
    #define PP_CSV_SEQ_PARENTHESISE_WITH_TOKEN(...) \
        BOOST_PP_SEQ_FOR_EACH_I(PP_PARENTHESISE_WITH_TOKEN, decltype, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
    
    #define PP_PERFORM_LOG_FORMAT_CHECK(fmt, ...) \
        Format<BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE((,##__VA_ARGS__)), 1), \
                BOOST_PP_EXPAND, PP_CSV_SEQ_PARENTHESISE_WITH_TOKEN)(__VA_ARGS__)>::check(fmt, 0)
    
    #define LOG(fmt, ...) \
        { \
            static_assert(PP_PERFORM_LOG_FORMAT_CHECK(fmt, ##__VA_ARGS__), ""); \
            boost::format f(fmt); \
            add(f, ##__VA_ARGS__); \
            std::cout << f.str() << std::endl; \
        }
    
    int main()
    {
        // nothing
        LOG("hello world");
    
        // char
        LOG("%c", 'x');
    
        // integral
        LOG("%d", -123);
        LOG("%ld", -123);
        LOG("%u", 123u);
        LOG("%lu", 123u);
    
        // strings
        LOG("%s", "hello world");
        LOG("%-s", "hello world");
        LOG("%s", std::string("hello world"));
        { const char* s = "hello world"; LOG("%s", s); }
        { std::string s = "hello world"; LOG("%s", s); }
        { std::string s = "hello world"; boost::string_ref r(s); LOG("%s", r); }
    
        // floating point
        LOG("%f", 1.23);
        LOG("%f", 1.23f);
    
        // width & precision
        LOG("%02d", 1);
        LOG("% 3s", "hello");
        LOG("% 3s", "yo");
        LOG("%.2d", 123);
        LOG("%.2f", 1.2345);
        LOG("%2f", 1.23456789);
        LOG("%02f", 0.1);
        LOG("%02.2f", 0.1);
    
        // width & precision as arguments
        // not supported by boost::format
    //    LOG("%*d", 3, 12);
    //    LOG("%.*s", 3, "hello");
    //    LOG("%.*d", 3, 12345);
    //    LOG("%*.*s", 3, 3, "hello");
    //    LOG("%*.*d", 3, 3, 12345);
    
        // mix of multiple different arguments
        LOG("%s", "hi");
        LOG("%s %d", "hi", 1);
        LOG("%s %d %u %lf %f %c", "hi", -1, 12u, 1.23, 1.33, 'c');
    
        // too few arguments
    //    LOG("%s %d %u %lf %f %c", "hi", -1, 12u, 1.23, 1.33);
    
        // too many arguments
    //    LOG("%s %d %u %lf %f %c", "hi", -1, 12u, 1.23, 1.33, 'c', 1);
    
        // incorrect argument for format string
    //    LOG("%s %d %u %lf %f %c", "hi", -1, 12u, 1, 1.33, 'c');
    }

I can probably improve the `%ll` length specifier check as this is currently implemented as a backwards search into the format string for 64-bit integral types. I also haven't done checking for `%hh` length specifier.

Any suggestions on this would be greatly appreciated.

##Error cases:

too few arguments:

    main.cpp:285:84: error: expression ‘<throw-expression>’ is not a constant-expression
                 : throw std::logic_error("too few arguments for provided format string");

too many arguments:

    main.cpp:257:87: error: expression ‘<throw-expression>’ is not a constant-expression
                     throw std::logic_error("too many arguments for provided format string")

mismatch between argument type and format:

    main.cpp:29:68: error: expression ‘<throw-expression>’ is not a constant-expression
         { return C != c ? throw std::logic_error("invalid fmt for type") : true; } \
                                                                        ^
    main.cpp:33:1: note: in expansion of macro ‘SUPPORTED_TYPE’
     SUPPORTED_TYPE(int,               'd');