#include #include #include #include #include #define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1) #define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N #define MACRO_DISPATCHER(func, ...) MACRO_DISPATCHER_(func, VA_NUM_ARGS(__VA_ARGS__)) #define MACRO_DISPATCHER_(func, NumArgs) MACRO_DISPATCHER__(func, NumArgs) #define MACRO_DISPATCHER__(func, NumArgs) func ## NumArgs #define SUPPORTED_TYPE2(T, C) \ template<> \ struct FormatSupportedType \ { \ constexpr static bool supports(char c) \ { return (c == C) \ ? true : throw std::logic_error("invalid fmt for type"); } \ } #define SUPPORTED_TYPE3(T, C1, C2) \ template<> \ struct FormatSupportedType \ { \ constexpr static bool supports(char c) \ { return (c == C1 || c == C2) \ ? true : throw std::logic_error("invalid fmt for type"); } \ } // general purpose binding macro which dispatches the arguments to the correct bind macro #define SUPPORTED_TYPE(...) MACRO_DISPATCHER(SUPPORTED_TYPE, __VA_ARGS__)(__VA_ARGS__) template struct FormatSupportedType; SUPPORTED_TYPE(char, 'c'); SUPPORTED_TYPE(int, 'd', '*'); SUPPORTED_TYPE(unsigned, 'u', '*'); SUPPORTED_TYPE(char*, 's'); SUPPORTED_TYPE(const char*, 's'); SUPPORTED_TYPE(std::string, 's'); SUPPORTED_TYPE(double, 'f'); SUPPORTED_TYPE(float, 'f'); ///////////////// constexpr bool isDigit(char c) { return c >= '0' && c <= '9'; } constexpr bool isModifier(char c) { return c == 'l' || c == 'h' || c == 'j' || c == 'z' || c == 't' || c == 'L' || c == '#' || c == '+' || c == '-' || c == ' ' || c == '\'' || c == 'I' || c == '.' || c == '=' || isDigit(c); } template constexpr size_t nextNonModifier(const char (&fmt)[N], std::size_t n) { return n >= N ? throw std::logic_error("invalid format string") : isModifier(fmt[n]) ? nextNonModifier(fmt, n + 1) : n; } //////////////////// // helpers for determining if the argument is a string template struct is_string_type { enum { value = false }; }; template<> struct is_string_type { enum { value = true }; }; template<> struct is_string_type { enum { value = true }; }; template<> struct is_string_type { enum { value = true }; }; //////////////////// template constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n); template constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n, const T& arg, const Ts&... args); //////////////////// template constexpr auto checkWidthAndPrecision(const char (&fmt)[N], std::size_t n, const T1& /*width*/, const T2& /*precision*/, const T3& /* arg */, const Ts&... args) -> typename std::enable_if< std::is_integral::value && std::is_integral::value, bool>::type { return FormatSupportedType< typename std::decay::type>::supports(fmt[n]) && checkFormatHelper(fmt, n + 1, args...); } template constexpr bool checkWidthAndPrecision(const char (&)[N], std::size_t, const Ts&...) { return false; } //////////////////// template constexpr auto checkWidthOrPrecision(const char (&fmt)[N], std::size_t n, const T1& /*precision*/, const T2& /* arg */, const Ts&... args) -> typename std::enable_if< std::is_integral::value, bool>::type { return FormatSupportedType< typename std::decay::type>::supports(fmt[n]) && checkFormatHelper(fmt, n + 1, args...); } template constexpr bool checkWidthOrPrecision(const char (&)[N], std::size_t, const Ts&...) { return false; } //////////////////// template constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n) { return n>= N ? true : fmt[n] != '%' ? checkFormatHelper(fmt, n + 1) : fmt[n + 1] == '%' ? checkFormatHelper(fmt, n + 2) : false; } template constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n, const T& arg, const Ts&... args) { return n >= N ? throw std::logic_error("too many arguments for provided format string") : fmt[n] != '%' ? checkFormatHelper(fmt, n + 1, arg, args...) // literal percent character : (fmt[n + 1] == '%') ? checkFormatHelper(fmt, n + 2, arg, args...) // long-long modifier : (fmt[n + 1] == 'l' && fmt[n + 2] == 'l') ? FormatSupportedType< typename std::decay::type >::supports(fmt[n + 3]) && checkFormatHelper(fmt, n + 4, args...) // width & precision modifier : (fmt[n + 1] == '*' && fmt[n + 2] == '.' && fmt[n + 3] == '*') ? checkWidthAndPrecision(fmt, n + 4, arg, args...) // width or precision modifier : ((fmt[n + 1] == '.' && fmt[n + 2] == '*') || (fmt[n + 1] == '*')) ? checkWidthOrPrecision(fmt, (fmt[n + 1] == '.' ? n + 3 : n + 2), arg, args...) // other modifier : (isModifier(fmt[n + 1])) ? FormatSupportedType< typename std::decay::type>::supports(fmt[nextNonModifier(fmt, n + 2)]) && checkFormatHelper(fmt, nextNonModifier(fmt, n + 2) + 1, args...) // no modifier : FormatSupportedType< typename std::decay::type>::supports(fmt[n + 1]) && checkFormatHelper(fmt, n + 2, args...); } template constexpr bool checkFormat(const char (&fmt)[N], const Ts&... args) { return checkFormatHelper(fmt, 0, args...); } // printing... void add(boost::format&) { } template void add(boost::format& f, const T& arg, const Ts&... ts) { f % arg; add(f, ts...); } #define LOG(fmt, ...) \ { \ static_assert(checkFormat(fmt, ##__VA_ARGS__), "Format is incorrect"); \ boost::format f(fmt); \ add(f, ##__VA_ARGS__); \ std::cout << f.str() << std::endl; \ } int main() { // char LOG("%c", 'x'); // integral LOG("%d", -123); LOG("%ld", -123); LOG("%u", 123u); LOG("%lu", 123u); // strings LOG("%s", "hello world"); { const char* s = "hello world"; LOG("%s", s); } { std::string s = "hello world"; LOG("%s", s); } // floating point LOG("%f", 1.23); LOG("%f", 1.23f); // width / precision LOG("%02d", 1); LOG("%.2d", 123); LOG("% 3s", "hello"); LOG("% 3s", "yo"); LOG("%.3s", "hello"); LOG("%.3s", "yo"); // incorrect format string // LOG("%f", 1); // LOG("%d", 1.23); // not supported by boost::format // LOG("%*s", 3, "yo"); // LOG("%*d", 3, 12); // LOG("%.*s", 3, "hello"); // LOG("%.*d", 3, 12345); // LOG("%*.*s", 3, 3, "hello"); // LOG("%*.*d", 3, 3, 12345); }