Ticket #6815: printf.cpp

File printf.cpp, 7.2 KB (added by anonymous, 8 years ago)

additional format type checking

Line 
1#include <cstddef>
2#include <cstdio>
3#include <stdexcept>
4#include <stdexcept>
5#include <boost/format.hpp>
6
7#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
8#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N
9
10#define MACRO_DISPATCHER(func, ...) MACRO_DISPATCHER_(func, VA_NUM_ARGS(__VA_ARGS__))
11#define MACRO_DISPATCHER_(func, NumArgs) MACRO_DISPATCHER__(func, NumArgs)
12#define MACRO_DISPATCHER__(func, NumArgs) func ## NumArgs
13
14#define SUPPORTED_TYPE2(T, C) \
15template<> \
16struct FormatSupportedType<T> \
17{ \
18 constexpr static bool supports(char c) \
19 { return (c == C) \
20 ? true : throw std::logic_error("invalid fmt for type"); } \
21}
22
23#define SUPPORTED_TYPE3(T, C1, C2) \
24template<> \
25struct FormatSupportedType<T> \
26{ \
27 constexpr static bool supports(char c) \
28 { return (c == C1 || c == C2) \
29 ? true : throw std::logic_error("invalid fmt for type"); } \
30}
31
32// general purpose binding macro which dispatches the arguments to the correct bind macro
33#define SUPPORTED_TYPE(...) MACRO_DISPATCHER(SUPPORTED_TYPE, __VA_ARGS__)(__VA_ARGS__)
34
35template<class>
36struct FormatSupportedType;
37
38SUPPORTED_TYPE(char, 'c');
39SUPPORTED_TYPE(int, 'd', '*');
40SUPPORTED_TYPE(unsigned, 'u', '*');
41SUPPORTED_TYPE(char*, 's');
42SUPPORTED_TYPE(const char*, 's');
43SUPPORTED_TYPE(std::string, 's');
44SUPPORTED_TYPE(double, 'f');
45SUPPORTED_TYPE(float, 'f');
46
47/////////////////
48
49constexpr bool isDigit(char c)
50{
51 return c >= '0' && c <= '9';
52}
53
54constexpr bool isModifier(char c)
55{
56 return c == 'l' ||
57 c == 'h' ||
58 c == 'j' ||
59 c == 'z' ||
60 c == 't' ||
61 c == 'L' ||
62 c == '#' ||
63 c == '+' ||
64 c == '-' ||
65 c == ' ' ||
66 c == '\'' ||
67 c == 'I' ||
68 c == '.' ||
69 c == '=' ||
70 isDigit(c);
71}
72
73template<std::size_t N>
74constexpr size_t nextNonModifier(const char (&fmt)[N], std::size_t n)
75{
76 return
77 n >= N ?
78 throw std::logic_error("invalid format string")
79 : isModifier(fmt[n]) ?
80 nextNonModifier(fmt, n + 1)
81 : n;
82}
83
84////////////////////
85
86// helpers for determining if the argument is a string
87template<class T> struct is_string_type { enum { value = false }; };
88template<> struct is_string_type<char*> { enum { value = true }; };
89template<> struct is_string_type<const char*> { enum { value = true }; };
90template<> struct is_string_type<std::string> { enum { value = true }; };
91
92////////////////////
93
94template<std::size_t N>
95constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n);
96template<std::size_t N, class T, class... Ts>
97constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n, const T& arg, const Ts&... args);
98
99////////////////////
100
101template<std::size_t N, typename T1, typename T2, typename T3, typename... Ts>
102constexpr auto checkWidthAndPrecision(const char (&fmt)[N], std::size_t n, const T1& /*width*/, const T2& /*precision*/, const T3& /* arg */, const Ts&... args)
103 -> typename std::enable_if<
104 std::is_integral<T1>::value &&
105 std::is_integral<T2>::value,
106 bool>::type
107{
108 return FormatSupportedType< typename std::decay<T3>::type>::supports(fmt[n]) &&
109 checkFormatHelper(fmt, n + 1, args...);
110}
111
112template<std::size_t N, typename... Ts>
113constexpr bool checkWidthAndPrecision(const char (&)[N], std::size_t, const Ts&...)
114{
115 return false;
116}
117
118////////////////////
119
120template<std::size_t N, typename T1, typename T2, typename... Ts>
121constexpr auto checkWidthOrPrecision(const char (&fmt)[N], std::size_t n, const T1& /*precision*/, const T2& /* arg */, const Ts&... args)
122 -> typename std::enable_if<
123 std::is_integral<T1>::value,
124 bool>::type
125{
126 return FormatSupportedType< typename std::decay<T2>::type>::supports(fmt[n]) &&
127 checkFormatHelper(fmt, n + 1, args...);
128}
129
130template<std::size_t N, typename... Ts>
131constexpr bool checkWidthOrPrecision(const char (&)[N], std::size_t, const Ts&...)
132{
133 return false;
134}
135
136////////////////////
137template<std::size_t N>
138constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n)
139{
140 return
141 n>= N ?
142 true
143 : fmt[n] != '%' ?
144 checkFormatHelper(fmt, n + 1)
145 : fmt[n + 1] == '%' ?
146 checkFormatHelper(fmt, n + 2)
147 : false;
148}
149
150template<std::size_t N, class T, class... Ts>
151constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n, const T& arg, const Ts&... args)
152{
153 return
154 n >= N ?
155 throw std::logic_error("too many arguments for provided format string")
156
157 : fmt[n] != '%' ?
158 checkFormatHelper(fmt, n + 1, arg, args...)
159
160 // literal percent character
161 : (fmt[n + 1] == '%') ?
162 checkFormatHelper(fmt, n + 2, arg, args...)
163
164 // long-long modifier
165 : (fmt[n + 1] == 'l' && fmt[n + 2] == 'l') ?
166 FormatSupportedType< typename std::decay<T>::type >::supports(fmt[n + 3]) &&
167 checkFormatHelper(fmt, n + 4, args...)
168
169 // width & precision modifier
170 : (fmt[n + 1] == '*' && fmt[n + 2] == '.' && fmt[n + 3] == '*') ?
171 checkWidthAndPrecision(fmt, n + 4, arg, args...)
172
173 // width or precision modifier
174 : ((fmt[n + 1] == '.' && fmt[n + 2] == '*') || (fmt[n + 1] == '*')) ?
175 checkWidthOrPrecision(fmt, (fmt[n + 1] == '.' ? n + 3 : n + 2), arg, args...)
176
177 // other modifier
178 : (isModifier(fmt[n + 1])) ?
179 FormatSupportedType< typename std::decay<T>::type>::supports(fmt[nextNonModifier(fmt, n + 2)]) &&
180 checkFormatHelper(fmt, nextNonModifier(fmt, n + 2) + 1, args...)
181
182 // no modifier
183 : FormatSupportedType< typename std::decay<T>::type>::supports(fmt[n + 1]) &&
184 checkFormatHelper(fmt, n + 2, args...);
185}
186
187template<std::size_t N, class... Ts>
188constexpr bool checkFormat(const char (&fmt)[N], const Ts&... args)
189{
190 return checkFormatHelper(fmt, 0, args...);
191}
192
193// printing...
194
195void add(boost::format&)
196{ }
197
198template<typename T, typename... Ts>
199void add(boost::format& f, const T& arg, const Ts&... ts)
200{
201 f % arg;
202 add(f, ts...);
203}
204
205#define LOG(fmt, ...) \
206 { \
207 static_assert(checkFormat(fmt, ##__VA_ARGS__), "Format is incorrect"); \
208 boost::format f(fmt); \
209 add(f, ##__VA_ARGS__); \
210 std::cout << f.str() << std::endl; \
211 }
212
213int main()
214{
215 // char
216 LOG("%c", 'x');
217
218 // integral
219 LOG("%d", -123);
220 LOG("%ld", -123);
221 LOG("%u", 123u);
222 LOG("%lu", 123u);
223
224 // strings
225 LOG("%s", "hello world");
226 { const char* s = "hello world"; LOG("%s", s); }
227 { std::string s = "hello world"; LOG("%s", s); }
228
229 // floating point
230 LOG("%f", 1.23);
231 LOG("%f", 1.23f);
232
233 // width / precision
234 LOG("%02d", 1);
235 LOG("%.2d", 123);
236 LOG("% 3s", "hello");
237 LOG("% 3s", "yo");
238 LOG("%.3s", "hello");
239 LOG("%.3s", "yo");
240
241 // incorrect format string
242 // LOG("%f", 1);
243 // LOG("%d", 1.23);
244
245 // not supported by boost::format
246 // LOG("%*s", 3, "yo");
247 // LOG("%*d", 3, 12);
248 // LOG("%.*s", 3, "hello");
249 // LOG("%.*d", 3, 12345);
250 // LOG("%*.*s", 3, 3, "hello");
251 // LOG("%*.*d", 3, 3, 12345);
252}
253