├── .gitignore ├── Makefile ├── README.md ├── __format_base.h ├── example.cc ├── ioformat ├── proposal ├── Makefile ├── examples.cc └── printf.md └── putf.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .*.swp 3 | *.o 4 | *.core 5 | tags 6 | *.html 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ccconf example CXX=g++49 CXXFLAGS+=-std=c++11 -Wall 2 | CXXFLAGS = -std=c++14 -Wall -g -I. -I/usr/local/include 3 | CXX = g++49 4 | 5 | .PHONY : all clean 6 | all : example 7 | clean : 8 | rm -f example example.o 9 | 10 | example : example.o 11 | ${CXX} ${LDFLAGS} -o example example.o 12 | example.o: example.cc putf.h __format_base.h 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # formatxx 2 | Printf for C++ 3 | 4 | This repository is **deprecated**. 5 | [xformat](https://github.com/lichray/xformat) 6 | is mature at doing this library's work. 7 | -------------------------------------------------------------------------------- /__format_base.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2012, 2013, 2015 Zhihao Yuan. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef ___FORMAT_BASE_H 27 | #define ___FORMAT_BASE_H 1 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #if defined(_MSC_VER) 35 | #include 36 | #endif 37 | 38 | namespace stdex { 39 | 40 | using std::get; 41 | 42 | template 43 | using identity = std::common_type; 44 | 45 | template 46 | struct _accept_narrow : std::false_type {}; 47 | 48 | template <> 49 | struct _accept_narrow : std::false_type {}; 50 | 51 | template 52 | struct _accept_narrow : std::true_type { 53 | typedef char char_type; 54 | typedef int int_type; 55 | }; 56 | 57 | template <> 58 | struct _accept_narrow : std::true_type { 59 | typedef signed char char_type; 60 | typedef int int_type; 61 | }; 62 | 63 | template <> 64 | struct _accept_narrow : std::true_type { 65 | typedef unsigned char char_type; 66 | typedef int int_type; 67 | }; 68 | 69 | template 70 | struct _param_type { 71 | using type = T const&; 72 | }; 73 | 74 | template 75 | struct _param_type::value>> { 76 | using type = T; 77 | }; 78 | 79 | template 80 | using _param_type_t = typename _param_type::type; 81 | 82 | template 83 | struct _fmt_put { 84 | typedef CharT const* iterator; 85 | 86 | _fmt_put(iterator it1, iterator it2, T const&... t) : 87 | iter_(it1, it2), item_(t...) {} 88 | 89 | iterator& begin() { 90 | return iter_.first; 91 | } 92 | 93 | iterator& end() { 94 | return iter_.second; 95 | } 96 | 97 | template 98 | friend auto _get(_fmt_put<_CharT, _T...> const& o) 99 | -> decltype(get<_I>(o.item_)); 100 | 101 | private: 102 | std::pair iter_; 103 | std::tuple<_param_type_t...> item_; 104 | }; 105 | 106 | template 107 | struct _tuple_size; 108 | 109 | template 110 | struct _tuple_size<_fmt_put> : 111 | std::integral_constant {}; 112 | 113 | template 114 | inline auto _get(_fmt_put const& o) 115 | -> decltype(get(o.item_)) { 116 | return get(o.item_); 117 | } 118 | 119 | template 120 | struct _visit1_at; 121 | 122 | template 123 | struct _visit1_at High)>> 124 | { 125 | template 126 | static decltype(auto) apply(int, T&&...) {} 127 | }; 128 | 129 | template 130 | struct _visit1_at 131 | { 132 | template 133 | static decltype(auto) apply(int n, F&& f, Tuple&& tp) { 134 | return std::forward(f)( 135 | _get(std::forward(tp))); 136 | } 137 | }; 138 | 139 | template 140 | struct _visit1_at> 141 | { 142 | template 143 | static decltype(auto) apply(int n, T&&... t) { 144 | if (n < Mid) 145 | return _visit1_at::apply(n, 146 | std::forward(t)...); 147 | else if (n == Mid) 148 | return _visit1_at::apply(n, 149 | std::forward(t)...); 150 | else 151 | return _visit1_at::apply(n, 152 | std::forward(t)...); 153 | } 154 | }; 155 | 156 | template 157 | inline decltype(auto) visit1_at(int n, F&& f, Tuple&& tp) { 158 | constexpr int m = _tuple_size>::value; 159 | assert(0 < n and n <= m); 160 | return _visit1_at<1, m>::apply(n, std::forward(f), 161 | std::forward(tp)); 162 | } 163 | 164 | template 165 | struct _indices {}; 166 | 167 | template 168 | struct _build_indices : _build_indices {}; 169 | 170 | template 171 | struct _build_indices<0, I...> : _indices {}; 172 | 173 | template 174 | using _tuple_indices = _build_indices::value>; 175 | 176 | template 177 | inline int _lexical_width(Int i) { 178 | if (i == 0) 179 | return 1; 180 | int n = 0; 181 | while (i != 0) { 182 | i /= Base; 183 | ++n; 184 | } 185 | return n; 186 | } 187 | 188 | template 189 | inline char _to_narrow_digit(CharT ch, Facet const& fac) { 190 | char c = fac.narrow(ch, 0); 191 | if ('0' <= c and c <= '9') 192 | return c; 193 | else 194 | return 0; 195 | } 196 | 197 | } 198 | 199 | #endif 200 | -------------------------------------------------------------------------------- /example.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using std::cout; 9 | using std::wcout; 10 | using stdex::putf; 11 | using stdex::vputf; 12 | using boost::format; 13 | 14 | int main() { 15 | double f = NAN; 16 | std::string s("astring"); 17 | cout << putf("%x %p %#o %#X\n", -1, &f, -1, -1); 18 | printf("%x %p %#o %#X\n", -1, &f, -1, -1); 19 | cout << putf("%e %f %g %a %E %F %G %A\n", f, f, f, f, f, f, f, f); 20 | printf("%e %f %g %a %E %F %G %A\n", f, f, f, f, f, f, f, f); 21 | f = 3.1415; 22 | cout << vputf("%e %f %g %a %E %F %G %A\n", std::tie(f, f, f, f, f, f, f, f)); 23 | printf("%e %f %g %a %E %F %G %A\n", f, f, f, f, f, f, f, f); 24 | cout << putf("%i %d %u %i %d %u\n", -1, -1, -1, -1, -1, -1); 25 | printf("%i %d %u %i %d %u\n", -1, -1, -1, -1, -1, -1); 26 | cout << vputf("%c %d %#.2f\n", std::make_tuple(64, 64, 0.1)); 27 | printf("%c %d %#.2f\n", 64, 64, 0.1); 28 | cout << putf("%12.4s|%14.2s|%.s\n", s.data(), s, s.data()); 29 | printf("%12.4s|%14.2s|%.s\n", s.data(), s.data(), s.data()); 30 | std::array args {{ -12, 6, 64, 12, 64, -6, 64 }}; 31 | cout << vputf("%*.*d|%#.*X|% 0*d\n", args); 32 | printf("%*.*d|%#.*X|% 0*d\n", -12, 6, 64, 12, 64, -6, 64); 33 | cout << putf("%1$d:%2$.*3$d:%4$*4$.*3$d\n", 12, 6, 2, 6); 34 | #ifndef _WIN32 35 | printf("%1$d:%2$.*3$d:%4$*4$.*3$d\n", 12, 6, 2, 6); 36 | #else 37 | _printf_p("%1$d:%2$.*3$d:%4$*4$.*3$d\n", 12, 6, 2, 6); 38 | #endif 39 | cout << putf("%2$s %3% %1%%5%\n", 12, s, f, '\0', false); 40 | cout << format("%2$s %3% %1%%5%\n") % 12 % s % f % '\0' % false; 41 | #ifndef _WIN32 42 | freopen(nullptr, "w", stdout); 43 | #endif 44 | wcout << putf(std::wstring(L"% 012d|%.f\n"), 1234567, f); 45 | wprintf(L"% 012d|%.f\n", 1234567, f); 46 | wcout << vputf(L"%-+012d|%+014p\n", std::make_pair(1234567, &f)); 47 | wprintf(L"%-+012d|%+014p\n", 1234567, &f); 48 | } 49 | -------------------------------------------------------------------------------- /ioformat: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2012, 2013 Zhihao Yuan. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef _IOFORMAT 27 | #define _IOFORMAT 1 28 | #include "putf.h" 29 | 30 | namespace std { 31 | namespace experimental { 32 | 33 | /* ADL interfaces */ 34 | 35 | template 36 | inline auto putf(std::basic_ostream& out, CharT const *fmt, 37 | T const&... t) -> decltype(out) 38 | { 39 | return out << stdex::putf(fmt, t...); 40 | } 41 | 42 | } 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /proposal/Makefile: -------------------------------------------------------------------------------- 1 | # ccconf examples CXX=g++48 CXXFLAGS+=-std=c++11 -Wall -g -I.. -I/usr/local/include 2 | CXXFLAGS = -std=c++11 -Wall -g -I.. -I/usr/local/include 3 | CXX = g++48 4 | 5 | .PHONY : all clean 6 | all : examples 7 | clean : 8 | rm -f examples examples.o tags 9 | 10 | tags : *.h examples.cc 11 | ctags *.h examples.cc 12 | 13 | examples : examples.o 14 | ${CXX} ${LDFLAGS} -o examples examples.o 15 | examples.o: examples.cc 16 | -------------------------------------------------------------------------------- /proposal/examples.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using std::cout; 7 | using std::left; 8 | using std::setprecision; 9 | using boost::format; 10 | 11 | namespace std { 12 | using std::experimental::putf; 13 | } 14 | 15 | int main() { 16 | putf(cout, "hello, %s\n", "world"); 17 | cout << format("The answer:%5d\n") % 42; 18 | putf(cout, "The answer:%5d\n", 42); 19 | putf(cout, "The answer:%*d\n", 5, 42); 20 | putf(cout, "The answer:%2$*1$d\n", 5, 42); 21 | putf(cout, "%2$s: %1$s\n", 42, "The answer"); 22 | putf(cout, "%2%: %1%\n", 42, "The answer"); 23 | putf(cout, "The answer:% -.4d\n", 42); 24 | printf("The answer:% -.4d\n", 42); 25 | cout << format("The answer:% -.4d\n") % 42; 26 | putf(cout, "The answer:% -.4f\n", 42); 27 | cout << "The answer:" << left << setprecision(4) << 42 << "\n"; 28 | } 29 | -------------------------------------------------------------------------------- /proposal/printf.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Doc. no.: N3716
Date: 2013-08-18
Project: Programming Language C++, Library Working Group
Reply-to: Zhihao Yuan <lichray at gmail dot com>
15 | 16 | # A printf-like Interface for the Streams Library (revision 1) 17 | 18 | * TOC 19 | {:toc} 20 | 21 | ## Changes since N3506 22 | 23 | - Support Boost.Format's simple positional syntax (`%1%`, `%2%` ...). 24 | - Cover the cornor cases "not-mentioned" by the C standard. 25 | - Mention the function style syntax suggested by BSI in "Future Issues". 26 | 27 | ## Overview 28 | 29 | cout << putf("hello, %s\n", "world"); 30 | 31 | Printf defines the most widely used syntax to format a text output. It exists 32 | in C, Perl, Python and even Java™, and is available from Qt to 33 | Boost.Format`[1]`, but not C++ standard library. This proposal tries to 34 | define such an interface based on the `printf` function defined by C`[2]` for 35 | the C++ I/O streams library, with the error handling policy and the type safety 36 | considered. 37 | 38 | ## Impact on the Standard 39 | 40 | The proposed new header `` makes no changes to the existing 41 | interface of the streams library, other than an `operator<<(basic_ostream)` 42 | overload to print the unspecified return value of a new `std::putf` function. 43 | However, the proposed formatting features are not parallel to those provided by 44 | the existing streams library. For short, the I/O manipulators can be fully 45 | replaced by the member functions of `ios_base`, while `std::putf` can not. 46 | 47 | The additional formatting features supported by `std::putf` are: 48 | 49 | - Empty sign `"% d"`. 50 | - Hexfloat with precision `"%.4a"`. 51 | - Integer with precision (minimal digits) `"%#5.2x"`. 52 | - String with precision (truncation, only for C-style strings) `"%.4s"`. 53 | 54 | ## Design Decisions 55 | 56 | The idea is to define a portable and readable syntax to enable the 57 | extensible formatting of the streams library, while allowing an implementation 58 | to perform any formatting without any extra buffering comparing to the `<<` 59 | operator. 60 | 61 | ### Syntax 62 | 63 | The syntax from printf in C is preserved as much as possible. Such an 64 | syntax is: 65 | 66 | - Compatible with C; works as a drop-in replacement of `printf` (except `%n`). 67 | - Compatible with the legacy syntax supported by Boost.Format. 68 | 69 | For example, both of the following 70 | 71 | cout << format("The answer:%5d\n") % 42; // boost.format 72 | cout << putf("The answer:%5d\n", 42); // std::experimental::putf 73 | 74 | print 75 | 76 | The answer: 42 77 | 78 | The _width_ `5` can be parameterized: 79 | 80 | cout << putf("The answer:%*d\n", 5, 42); // same effect 81 | 82 | This mechanism is supported by both C and POSIX, but not Boost.Format. 83 | 84 | POSIX`[4]` style positional arguments are added because they are necessary 85 | for i18n. 86 | 87 | So the example above can be rewrote into: 88 | 89 | cout << putf("The answer:%2$*1$d\n", 5, 42); // same effect 90 | 91 | The `%n` specification is dropped because of the security problem (and its 92 | weird semantics); no known printf fork (in Java™, Python, Boost.Format, 93 | etc.) supports it. 94 | 95 | However, Boost.Format's position-only specification `%`_`N`_`%` is supported. 96 | So instead of writing 97 | 98 | cout << putf("%2$s: %1$s\n", 42, "The answer"); 99 | 100 | , user can just say 101 | 102 | cout << putf("%2%: %1%\n", 42, "The answer"); 103 | 104 | Such a syntax has been widely used in industry, and does not break the 105 | printf-compatibility. 106 | 107 | C++ streams style error handling policy and type safety requirements are 108 | satisfied with the highest priority. However, that makes the _length 109 | modifiers_ (`hh`, `h`, `l`, `ll`, `j`, `z`, `t`, `L`) unneeded. The proposed 110 | solution is to ignore them, like Boost.Format and Python`[3]`, while the only 111 | difference is that, we completely ignore all of them according to the C 112 | standard, not just a subset. 113 | 114 | ### Extensibility 115 | 116 | A subset of the printf format specification can be translated into a 117 | combination of the formatting properties (`flags()`, `width()`, `precision()` 118 | and `fill()`) of an output stream. To balance the standard compliance and the 119 | extensibility, this proposal distinguishes the arguments to be printed into: 120 | 121 | - _internally formattable_, which have the same formatting as if they are 122 | formatted by `snprintf` or a wide character equivalence given the same 123 | format specifications with a fitted length modifier, and 124 | - _potentially formattable_, which will be outputted by the `<<` operator 125 | with the translated formatting properties set up on the output stream. 126 | 127 | If an argument is internally formattable by a format specification, then C's 128 | formatting is fully supported. For example, the following 129 | 130 | cout << putf("The answer:% -.4d\n", 42); // empty sign, left alignment, 4 minimal digits 131 | 132 | has the same printing result as 133 | 134 | printf("The answer:% -.4d\n", 42); 135 | 136 | which gives 137 | 138 | The answer: 0042 139 | 140 | , while Boost.Format gives 141 | 142 | The answer: 42 143 | 144 | without an integer precision support. 145 | 146 | But if an argument is potentially formattable by a specification, the 147 | following 148 | 149 | cout << putf("The answer:% -.4f\n", 42); // expects a floating point 150 | 151 | has the same printing result as 152 | 153 | cout << "The answer:" << left << setprecision(4) << 42 << "\n" 154 | 155 | which gives 156 | 157 | The answer:42 158 | 159 | since there is no "empty sign" support in the streams library. 160 | 161 | A detailed description is available in [Formatting](#formatting). 162 | 163 | ## Technical Specifications 164 | 165 | *The description below is based on POSIX`[4]`.* 166 | 167 | `std::putf` takes a format string, followed by zero or more arguments. A 168 | format string is composed of zero or more directives: _ordinary characters_, 169 | which are copied unchanged to the output stream, and _format specifications_, 170 | each of which expects zero or more arguments. 171 | 172 | An empty format specification `%%` matches no argument; a `'%'` character is 173 | printed without formatting. 174 | 175 | A numbered format specification introduced by `"%`_`n`_`$"` matches the _n_th 176 | argument in the argument list, where _n_ is a decimal integer. 177 | 178 | A number-only format specification, `"%`_`n`_`%"`, behaviors as same as a 179 | numbered format specification `"%`_`n`_`$s"`, where `s` is a _type hint_ 180 | described below. 181 | 182 | An unnumbered format specification introduced by `'%'` matches the first 183 | unmatched argument in the argument list. 184 | 185 | Matching an out-of-range argument in a format string results in an error 186 | described in [Error handling](#error_handling), while the unmatched arguments 187 | are ignored. An argument can be matched multiple times by a format string 188 | of the numbered format specifications. 189 | 190 | The character sequence `"%`_`n`_`$"` or the `'%'` character, introducing a 191 | format specification, has the following appear in sequence: 192 | 193 | - Zero or more _flags_ (in any order). 194 | - An optional minimum _field width_, which takes either a parameterized length 195 | ( `'*'` or `"*`_`n`_`$"`), described below, or a decimal integer. 196 | - An optional _precision_, which takes the form of a period ( `'.'` ) 197 | followed either by a parameterized length ( `'*'` or `"*`_`n`_`$"` ), 198 | described below, or an 199 | optional decimal digit string, where a null digit string is treated as zero. 200 | - An optional length modifier (ignored). 201 | - A _type hint_ character that indicates the type of the matched argument. 202 | 203 | A field width, or precision, or both, may be indicated by a numbered 204 | parameterized length ( `"*`_`n`_`$"` ), which is allowed within a numbered 205 | format specification, or an unnumbered parameterized length ( `'*'` ), which is 206 | allowed within an unnumbered format specification. In such cases an argument 207 | of type `streamsize` supplies the field width or precision. A numbered 208 | parameterized length matches the _n_th argument in the argument list, where 209 | _n_ is a decimal integer. The unnumbered parameterized lengths, in their order 210 | of appearance, match the unmatched arguments in the argument list, before the 211 | format specification they belong to. 212 | A negative field width is taken as a `'-'` flag followed 213 | by a positive field width. A negative precision is taken as if the precision 214 | were omitted. 215 | 216 | A format string can contain either numbered format specifications, or 217 | unnumbered format specifications, but not both. Mixing numbered and 218 | unnumbered specifications or parameterized lengths result in an error described 219 | in [Error handling](#error_handling). The empty format specification `%%` can 220 | be mixed with any specifications. 221 | 222 | ### Header `` 223 | 224 | namespace std { 225 | namespace experimental { 226 | 227 | // types _Ts1_ and _Ts2_ are sets of implementation types which are distinguishable for different T... 228 | 229 | template 230 | _Ts1_ putf(CharT const *fmt, T const&... t); 231 | 232 | template 233 | _Ts2_ putf(basic_string const& fmt, T const&... t); 234 | 235 | template 236 | auto operator<<(basic_ostream& os, _Ts1_or_Ts2_ bundle) 237 | -> decltype(os); 238 | 239 | }} 240 | 241 | The output functions of the return values of `std::putf` do formatted output, 242 | but behavior like the _unformatted output functions_. Specifically, `flags()`, 243 | `width()`, `precision()` and `fill()` of the output stream are preserved when 244 | the flow of control leaves these functions, but may be changed during the 245 | execution. Changing the return values of these members before the execution 246 | takes no effect to the printing, except: 247 | 248 | - `flags() & ios_base::unitbuf` may change the buffering behavior. 249 | - `fill()` works as the default padding character. 250 | 251 | ### Error handling 252 | 253 | An output function of a return value of `std::putf` may encounter the 254 | following kinds of errors found in the return value: 255 | 256 | - A format specification is syntactically invalid. 257 | - A format specification expects an argument that does not appear 258 | in the argument list. 259 | - Mixing numbered and unnumbered format specifications or parameterized 260 | lengths. 261 | - The argument matched a parameterized length is not convertible to 262 | `streamsize`. 263 | 264 | The output function set `ios_base::failbit` on the output stream when 265 | one of the errors is encountered, and then can return. The well matched format 266 | specifications, as well as the ordinary characters, if any, before the format 267 | specification that fails, must be formatted and wrote to the output stream 268 | before the function returns. 269 | 270 | ### Formatting 271 | 272 | For a given format description, the matched argument is _potentially 273 | formattable_ if: 274 | 275 | - the _flags_ contains **`+`**, or _space_, or both, but the type hint is not 276 | one of `d`, `i`, `a`, `A`, `e`, `E`, `f`, `F`, `g`, and `G`, or 277 | - the _flags_ contains **`#`**, but the type hint is not one of 278 | `o`, `x`, `X`, `a`, `A`, `e`, `E`, `f`, `F`, `g`, and `G`, or 279 | - the _flags_ contains **`0`**, but the type hint is not one of 280 | `d`, `i`, `o`, `u`, `x`, `X`, `a`, `A`, `e`, `E`, `f`, `F`, `g`, and `G`. 281 | 282 | If not, regarding the output stream type `basic_ostream`, the 283 | matched argument is _internally formattable_ if: 284 | 285 | - the type hint is `d`, `i`, `o`, `u`, `x`, or `X`, and the argument is an 286 | integer, or 287 | - the type hint is `a`, `A`, `e`, `E`, `f`, `F`, `g`, or `G`, and the 288 | argument is a floating-point number, or 289 | - the type hint is `p`, and the argument is a pointer, or 290 | - the type hint is `c`, and the argument is `char`, `CharT`, or 291 | `Traits::int_type`, or `signed char`/`unsigned char` if `CharT` is `char`, 292 | or 293 | - the type hint is `s`, and the argument is `const char*`, `const CharT*`, 294 | or `const signed char*`/`const unsigned char*` if `CharT` is `char`. 295 | 296 | *\[Note: An internally formattable argument has an `operator<<` overload, 297 | member or non-member, in the `` header, and can be printted by 298 | `printf` without a type-unsafe conversion. This note also applys to 299 | `Traits::int_type`, considering its underlying type. --end note\]* 300 | 301 | Otherwise, the argument is _potentially formattable_. 302 | 303 | If an _internally formattable_ argument is an unsigned integer and the type 304 | hint is `d` or `i`, the argument is printed as if it is formatted by `snprintf` 305 | or a wide character equivalence, which conceptually uses a default padding 306 | character of `os.fill()`, given the same _flags_, _field-width_, and 307 | _precision_, if any, respectively, followed by a fitted length modifier, if 308 | needed, and a _type hint_ of `u`. Otherwise, the argument is printed as if it 309 | is formatted by `snprintf` or a wide character equivalence, which conceptually 310 | uses a default padding character of `os.fill()`, given the same 311 | _flags_, _field-width_, and _precision_, if any, respectively, followed by a 312 | fitted length modifier, if needed, and the same _type hint_. *\[Note: `u`, 313 | `o`, `x`, `X` convert a signed argument to unsigned, while `d` and `i` do not 314 | convert an unsigned argument to signed. --end note\]* 315 | 316 | If the argument is _potentially formattable_, `width()` and `precision()` of 317 | the output stream are defaulted to `0` and `-1`, respectively. The `flags()` 318 | member is defaulted to `os.flags() & ios_base::unitbuf`, and the `fill()` 319 | member is defaulted to the saved fill character of the output stream before 320 | entering the current output function. 321 | 322 | For a given format description, if the argument is _potentially formattable_, 323 | for the following _flag_ characters, the argument is formatted as if the 324 | corresponding actions are taken on the output stream: 325 | 326 | - **`-`** sets `ios_base::left`. 327 | - **`+`** sets `ios_base::showpos`. 328 | - _space_ has no effect. 329 | - **`#`** sets `ios_base::showbase` and `ios_base::showpoint`. 330 | - **`0`** sets `fill()` to `'0'` and sets `ios_base::internal`, only if the 331 | `'-'` flag does not appear in the flags, and a precision is not specified if 332 | the type hint is `d`, `i`, `o`, `u`, `x`, or `X`. 333 | 334 | Under the same preconditions, the _field-width_ field, if any, sets the 335 | `width()` member of the output stream; and the _precision_ field, if any, sets 336 | the `precision()` member of the output stream. *\[Note: The cases of a 337 | negative _field-width_ or _precision_ are described in [Technical 338 | Specifications](#technical_specifications). --end note\]* 339 | 340 | Under the same preconditions, the _type hint_ characters and their effects on 341 | the output stream are: 342 | 343 | - **`d`** sets `ios_base::dec`. 344 | - **`i`** has no effect (`os.flags() & ios_base::basefield == 0`). 345 | - **`u`** sets `ios_base::dec`. 346 | - **`o`** sets `ios_base::oct`. 347 | - **`x`** sets `ios_base::hex`. 348 | - **`X`** sets `ios_base::hex | ios_base::uppercase`. 349 | - **`f`** sets `ios_base::fixed`. 350 | - **`F`** sets `ios_base::fixed | ios_base::uppercase`. 351 | - **`e`** sets `ios_base::scientific`. 352 | - **`E`** sets `ios_base::scientific | ios_base::uppercase`. 353 | - **`g`** has no effect (`os.flags() & ios_base::floatfield == 0`) 354 | - **`G`** sets `ios_base::uppercase`. 355 | - **`a`** sets `ios_base::fixed | ios_base::scientific`. 356 | - **`A`** sets `ios_base::fixed | ios_base::scientific | ios_base::uppercase`. 357 | - **`c`** has no effect. 358 | - **`s`** sets `ios_base::boolalpha`. 359 | - **`p`** has no effect. 360 | 361 | And then, the _potentially formattable_ argument, namely `t`, is printed by 362 | calling `os << t`. 363 | 364 | ### Wording 365 | 366 | This is an initial report; a wording can be prepared after a further 367 | discussion. 368 | 369 | ## Sample Implementation 370 | 371 | A sample implementation is available at 372 | 373 | 374 | One known defect in this implementation is that the `%a` and `%A` format 375 | specifications ignore the precision when printing a floating point argument. 376 | 377 | ### Performance notes 378 | 379 | The additional runtime performance costs comparing with the streams library 380 | are caused by parsing the format string and creating the formatting guards (to 381 | restore the flags, precision, etc., after formatting each specifications, 382 | exception-safely). In addition, to access a positional argument numbered 383 | _N_, _N - 1_ empty recursions are required to locate the correct template 384 | instantiation. 385 | 386 | In the sample implementation, some extra copying are involved to emulate 387 | `printf`'s formatting features using streams. However, the _internally 388 | formattable_ arguments are internally supported by the streams library, so a 389 | standard library implementation must be able to avoid these costs. For 390 | example, to print a string with precision, the sample implementation has to 391 | copy the string, while `libstdc++` already has an internal interface 392 | `__ostream_insert()` which takes a size parameter. These costs are not 393 | shown by the benchmark below, and Boost.Format does the same thing, actually. 394 | 395 | Here is a benchmark using Boost.Format's test code, release mode: 396 | 397 | Non-positional arguments/normal: 398 | 399 | printf time :0.367188 400 | ostream time :0.59375, = 1.61702 * printf 401 | format time :2.125, = 5.78723 * printf , = 3.57895 * nullStream 402 | std::putf time :0.90625, = 2.46809 * printf , = 1.52632 * nullStream 403 | 404 | Positional arguments/normal: 405 | 406 | printf time :0.414062 407 | ostream time :0.59375, = 1.43396 * printf 408 | format time :2.11719, = 5.11321 * printf , = 3.56579 * nullStream 409 | std::putf time :1.00781, = 2.43396 * printf , = 1.69737 * nullStream 410 | 411 | Environment: 412 | 413 | FreeBSD 8.3-STABLE amd64 414 | g++ 4.8.0 20121209 415 | Boost 1.48.0 416 | 417 | *Explanations*: 418 | 419 | *The two test cases take the same amount of arguments, and have the same 420 | formatting results. The streams library has no such "positional arguments", 421 | so I reordered the arguments by hand. * 422 | 423 | *"normal" means the locale is turned on. However, I did not see a stable 424 | difference between `normal` and `no_locale`.* 425 | 426 | *The format object of boost can be 427 | reused, which brings a performance increase around %17. Such a "feature" 428 | is not applicable to `printf` or `std::putf`, so I did not include them.* 429 | 430 | ## Future Issues 431 | 432 | 1. By overloading `std::printf` with a `basic_ostream` as the first 433 | argument, we can get an ADL-capable interface which is similiar to 434 | `std::getline`: 435 |
printf(std::wcout, L"%d\n", 42);
436 | I suggest to add this interface in addition to `std::putf`; one for 437 | `printf` transition, one for `boost::format` transition. 438 | 439 | 2. Do we need the `vprintf`-like interfaces, like, to take the tuples of 440 | arguments? If so, use a special flag or just more function names? For 441 | reference, check `std::experimental::vputf` in the sample implementation. 442 | 443 | 3. Is an `scanf` equivalence, e.g., `std::getf`, worth to be added? 444 | 445 | ## Acknowledgments 446 | 447 | Andrew Sandoval, who gave me some suggestions on standard-compliance and 448 | error handling. 449 | 450 | Herb Sutter, who encouraged me to prepare the proposal, suggested me to add 451 | the positional arguments, and even provided many suggestions and corrections 452 | on the proposal. 453 | 454 | Many people in the "std-proposals" mailing list: Jeffrey Yasskin, who 455 | "enforced" me to add the positional arguments; Martin Desharnais, who gave me 456 | the link about how to implement one; and many others. 457 | 458 | Roger Orr, who gave me the thought about the `std::getline`-like interface. 459 | 460 | 461 | ## References 462 | 463 | `[1]` The Boost Format library. 464 | 465 | 466 | `[2]` The `fprintf` function. _ISO/IEC 9899:2011_. 7.21.6.1. 467 | 468 | `[3]` String Formatting Operations. _The Python Standard Library_. 5.6.2. 469 | 470 | 471 | `[4]` dprintf, fprintf, printf, snprintf, sprintf - print formatted output. 472 | _IEEE Std 1003.1-2008_. 473 | 474 | -------------------------------------------------------------------------------- /putf.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2012, 2013, 2015 Zhihao Yuan. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef _PUTF_H 27 | #define _PUTF_H 1 28 | 29 | #include "__format_base.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | namespace stdex { 36 | 37 | template 38 | inline auto putf(CharT const *fmt, T const&... t) 39 | -> _fmt_put...> { 40 | return { fmt, fmt + std::char_traits::length(fmt), t... }; 41 | } 42 | 43 | template 44 | inline auto putf(std::basic_string const& fmt, 45 | T const&... t) 46 | -> _fmt_put...> { 47 | return { fmt.data(), fmt.data() + fmt.size(), t... }; 48 | } 49 | 50 | template 51 | inline auto _vputf(CharT const *fmt, Tuple const& t, _indices) 52 | -> _fmt_put>...> { 53 | return { fmt, fmt + std::char_traits::length(fmt), 54 | get(t)... }; 55 | } 56 | 57 | template 59 | inline auto _vputf(std::basic_string const& fmt, 60 | Tuple const& t, _indices) 61 | -> _fmt_put>...> { 62 | return { fmt.data(), fmt.data() + fmt.size(), get(t)... }; 63 | } 64 | 65 | template 66 | inline auto vputf(CharT const *fmt, Tuple const& t) { 67 | return _vputf(fmt, t, _tuple_indices()); 68 | } 69 | 70 | template 71 | inline auto vputf(std::basic_string const& fmt, 72 | Tuple const& t) { 73 | return _vputf(fmt, t, _tuple_indices()); 74 | } 75 | 76 | template 77 | struct _make_unsigned_fallback; 78 | 79 | template <> 80 | struct _make_unsigned_fallback { 81 | using type = bool; 82 | }; 83 | 84 | template 85 | struct _make_unsigned_fallback::value>> { 87 | using type = std::make_unsigned_t; 88 | }; 89 | 90 | template 91 | inline T const& _to_unsigned(T const& t, 92 | std::enable_if_t::value>* = 0) { 93 | return t; 94 | } 95 | 96 | template 97 | inline auto _to_unsigned(T t) 98 | -> typename _make_unsigned_fallback::type { 99 | return t; 100 | } 101 | 102 | template 103 | inline T const& _to_char(T const& t, 104 | std::enable_if_t::value>* = 0) { 106 | return t; 107 | } 108 | 109 | template 110 | inline auto _to_char(T t, 111 | std::enable_if_t::value>* = 0) 113 | -> typename Traits::char_type { 114 | return Traits::to_char_type(t); 115 | } 116 | 117 | template 118 | inline T const& _to_int(T const& t, 119 | std::enable_if_t::value and 121 | not _accept_narrow::value>* = 0) { 122 | return t; 123 | } 124 | 125 | template 126 | inline auto _to_int(T t, 127 | std::enable_if_t::value>* = 0) 129 | -> typename Traits::int_type { 130 | return Traits::to_int_type(t); 131 | } 132 | 133 | template 134 | inline auto _to_int(T t) 135 | -> typename _accept_narrow::int_type { 136 | return t; 137 | } 138 | 139 | template 140 | inline void _streamsize_or_not(T const&, U&, 141 | std::enable_if_t::value or std::is_enum::value) and 143 | std::is_convertible::value)>* = 0) {} 144 | 145 | inline void _streamsize_or_not(std::streamsize t, 146 | std::pair& sz) { 147 | sz = { true, t }; 148 | } 149 | 150 | template 151 | inline int _parse_int(Iter& b, Iter& e, Facet const& fac) { 152 | int n = 0; 153 | for (char c; b != e and (c = _to_narrow_digit(*b, fac)); ++b) { 154 | n *= 10; 155 | n += c - '0'; 156 | } 157 | return n; 158 | } 159 | 160 | template 161 | inline int _parse_position(Iter& b, Iter& e, Facet const& fac) { 162 | auto ob = b; 163 | int n = _parse_int(b, e, fac); 164 | if (n == 0 or b == e or *b != fac.widen('$')) { 165 | b = ob; 166 | return 0; 167 | } 168 | ++b; 169 | return n; 170 | } 171 | 172 | template 173 | inline auto _flags_for_output(Stream const& out) { 174 | using os = Stream; 175 | return out.flags() & os::unitbuf; 176 | } 177 | 178 | template 179 | struct _padding { 180 | template 181 | explicit _padding(Stream const& s) : 182 | precision_(s.precision()), fill_(s.fill()) {} 183 | 184 | std::streamsize precision_; 185 | CharT fill_; 186 | bool align_sign_ = false; 187 | }; 188 | 189 | template 190 | struct _padding_guard { 191 | typedef typename Stream::char_type char_type; 192 | typedef _padding padding; 193 | 194 | _padding_guard(Stream& s, padding pad) : 195 | stream_(s), pad_(s) { 196 | stream_.precision(pad.precision_); 197 | stream_.fill(pad.fill_); 198 | } 199 | ~_padding_guard() { 200 | stream_.fill(pad_.fill_); 201 | stream_.precision(pad_.precision_); 202 | } 203 | 204 | _padding_guard(_padding_guard const&) = delete; 205 | _padding_guard& operator=(_padding_guard const&) = delete; 206 | 207 | private: 208 | Stream& stream_; 209 | padding pad_; 210 | }; 211 | 212 | template 213 | class _outputter; 214 | 215 | template 216 | inline auto _output(Stream& out, T const& t) 217 | -> _outputter { 218 | return { out, t }; 219 | } 220 | 221 | template 222 | class _outputter { 223 | template 224 | friend class _outputter; 225 | 226 | using RealT = std::conditional_t::value, 227 | std::remove_const_t>*, T>; 228 | 229 | Stream& out_; 230 | _param_type_t t_; 231 | 232 | public: 233 | typedef typename Stream::char_type char_type; 234 | typedef typename Stream::traits_type traits_type; 235 | typedef typename Stream::fmtflags fmtflags; 236 | typedef _padding padding; 237 | 238 | _outputter(Stream& out, T const& t) : out_(out), t_(t) {} 239 | 240 | Stream& with(fmtflags fl, padding pad) { 241 | return _with(fl, pad, identity()); 242 | } 243 | 244 | template 245 | auto with_aligned_sign(fmtflags fl, padding pad) 246 | -> std::enable_if_t::value, Stream&> { 247 | return _with(fl, pad, identity()); 248 | } 249 | 250 | template 251 | auto with_aligned_sign(fmtflags fl, padding pad) 252 | -> std::enable_if_t::value, Stream&> { 253 | using os = std::basic_ostringstream; 254 | 255 | os dummy_out; 256 | fl |= os::showpos; 257 | 258 | // simulate `out_' 259 | dummy_out.width(out_.width(0)); 260 | dummy_out.fill(out_.fill()); 261 | dummy_out.imbue(out_.getloc()); 262 | _output(dummy_out, t_)._with(fl, pad, identity()); 263 | auto s = dummy_out.str(); 264 | auto i = s.find(out_.widen('+')); 265 | if (i != decltype(s)::npos) 266 | s[i] = dummy_out.fill(); 267 | out_.write(s.data(), s.size()); 268 | return out_; 269 | } 270 | 271 | private: 272 | template 273 | Stream& _with(fmtflags fl, padding pad, identity<_T>, 274 | std::enable_if_t::value or 275 | _accept_narrow::value>* = 0) { 276 | return _output__(fl, pad, t_); 277 | } 278 | 279 | template 280 | Stream& _with(fmtflags fl, padding pad, identity<_T>, 281 | std::enable_if_t::value>* = 0) { 282 | if (fl & Stream::internal and !std::isfinite(t_)) 283 | pad.fill_ = out_.fill(); 284 | return _output__(fl, pad, t_); 285 | } 286 | 287 | template 288 | Stream& _with(fmtflags fl, padding pad, identity<_T>, 289 | std::enable_if_t::value and 290 | not _accept_narrow::value>* = 0) { 291 | return _output_int__(fl, pad); 292 | } 293 | 294 | Stream& _with(fmtflags fl, padding pad, identity) { 295 | return _output__(fl, pad, t_); 296 | } 297 | 298 | Stream& _with(fmtflags fl, padding pad, identity) { 299 | return _output__(fl, pad, t_); 300 | } 301 | 302 | Stream& _with(fmtflags fl, padding pad, identity) { 303 | return _output_chars__(fl, pad); 304 | } 305 | 306 | template 307 | Stream& _with(fmtflags fl, padding pad, identity<_CharT *>, 308 | typename _accept_narrow::char_type* = 0) { 309 | return _output_chars__(fl, pad); 310 | } 311 | 312 | Stream& _output_int__(fmtflags fl, padding pad) { 313 | using os = std::basic_ostringstream; 314 | 315 | if (fl & os::showbase and fl & os::oct and pad.precision_ < 1) 316 | pad.precision_ = 1; 317 | if (pad.precision_ == 0 and t_ == 0) 318 | return _output__(fl, pad, ""); 319 | if (pad.precision_ <= 1) 320 | return _output__(fl, pad, t_); 321 | 322 | int w = fl & os::hex ? _lexical_width<16>(t_) : 323 | fl & os::oct ? _lexical_width<8>(t_) + 324 | !!(fl & os::showbase) : 325 | _lexical_width<10>(t_); 326 | if (pad.precision_ <= w) 327 | return _output__(fl, pad, t_); 328 | 329 | os dummy_out; 330 | 331 | // print without width 332 | dummy_out.imbue(out_.getloc()); 333 | _output(dummy_out, t_)._output__(fl, pad, t_); 334 | auto s = dummy_out.str(); 335 | s.insert(s.size() - w, size_t(pad.precision_ - w), 336 | out_.widen('0')); 337 | return _output__(fl, pad, s); 338 | } 339 | 340 | Stream& _output_chars__(fmtflags fl, padding pad) { 341 | using _CharT = std::remove_pointer_t; 342 | 343 | if (pad.precision_ < 0) 344 | return _output__(fl, pad, t_); 345 | 346 | size_t n = 0; 347 | auto i = t_; 348 | for (; *i and n < size_t(pad.precision_); ++i) 349 | ++n; 350 | if (*i != 0) 351 | return _output__(fl, pad, 352 | std::basic_string<_CharT>(t_, n).data()); 353 | else 354 | return _output__(fl, pad, t_); 355 | } 356 | 357 | template 358 | Stream& _output__(fmtflags fl, padding pad, _T const& t) { 359 | _padding_guard _(out_, pad); 360 | out_.flags(fl); 361 | out_ << t; 362 | return out_; 363 | } 364 | }; 365 | 366 | template 367 | struct _put_fmtter; 368 | 369 | template 370 | inline auto _put_fmt(std::basic_ostream& out, Args... args) { 371 | return _put_fmtter(out, args...); 372 | } 373 | 374 | enum class spec { 375 | none, 376 | to_unsigned, 377 | to_char, 378 | to_int, 379 | }; 380 | 381 | template 382 | struct _put_fmtter { 383 | using os = std::basic_ostream; 384 | 385 | typedef typename os::fmtflags fmtflags; 386 | typedef _padding padding; 387 | 388 | explicit _put_fmtter(os& s) : out(s), argN(0) {} 389 | 390 | template 391 | auto from(_fmt_put& t) -> os& 392 | { 393 | using std::begin; using std::end; 394 | 395 | auto& b = begin(t); 396 | auto i = b; 397 | auto& fac = std::use_facet>(out.getloc()); 398 | 399 | if (b == end(t)) 400 | return out; 401 | i = std::find(b, end(t), out.widen('%')); 402 | out.write(&*b, i - begin(t)); 403 | if (i == end(t)) { 404 | // ignore excess arguments 405 | return out; 406 | } 407 | b = ++i; 408 | if (b == end(t)) { 409 | out.setstate(os::failbit); // only 1 trailing % 410 | return out; 411 | } 412 | 413 | if (*b == out.widen('%')) { 414 | ++b; 415 | out.put(out.widen('%')); 416 | return from(t); 417 | } 418 | 419 | auto sp = spec::none; 420 | auto fl = _flags_for_output(out); 421 | auto pad = padding(out); 422 | pad.precision_ = -1; 423 | 424 | auto _ignore_zero_padding = [&] { 425 | fl &= ~os::internal; 426 | pad.fill_ = out.fill(); 427 | }; 428 | 429 | auto _bad_indexing = [&](int i) { 430 | if (sequential) 431 | return i != 0 or argN >= int(sizeof...(T)); 432 | else 433 | return i == 0 or i > int(sizeof...(T)); 434 | }; 435 | 436 | { 437 | auto ob = b; 438 | int ti = _parse_int(b, end(t), fac); 439 | if (ti == 0 or b == end(t) or 440 | (*b != fac.widen('$') and 441 | *b != fac.widen('%'))) { 442 | ti = 0; 443 | b = ob; 444 | } 445 | if (argN == 0) // access is not known yet 446 | sequential = (ti == 0); 447 | if (_bad_indexing(ti)) { 448 | out.setstate(os::failbit); 449 | return out; 450 | } 451 | if (ti == 0) 452 | ++argN; 453 | else 454 | { 455 | argN = ti; 456 | 457 | if (*b++ == out.widen('%')) { 458 | visit1_at(argN, [&](auto&& x) 459 | { _output(out, x).with(fl, pad); }, 460 | t); 461 | return from(t); 462 | } 463 | } 464 | } 465 | 466 | parse_flags: 467 | if (b == end(t)) { 468 | out.setstate(os::failbit); // incomplete spec 469 | return out; 470 | } 471 | 472 | switch (out.narrow(*b, 0)) { 473 | case '-': 474 | _ignore_zero_padding(); 475 | fl |= os::left; 476 | break; 477 | case '+': 478 | pad.align_sign_ = false; 479 | fl |= os::showpos; 480 | break; 481 | case ' ': 482 | if (!(fl & os::showpos)) 483 | pad.align_sign_ = true; 484 | break; 485 | case '#': 486 | fl |= os::showbase | os::showpoint; 487 | break; 488 | case '0': 489 | if (!(fl & os::left)) { 490 | fl |= os::internal; 491 | pad.fill_ = out.widen('0'); 492 | } 493 | break; 494 | default: 495 | goto parse_width; 496 | } 497 | ++b; 498 | goto parse_flags; 499 | 500 | parse_width: 501 | if (_to_narrow_digit(*b, fac)) 502 | out.width(_parse_int(b, end(t), fac)); 503 | else if (*b == out.widen('*')) { 504 | ++b; 505 | int ti = _parse_position(b, end(t), fac); 506 | if (_bad_indexing(ti)) { 507 | out.setstate(os::failbit); 508 | return out; 509 | } 510 | if (ti == 0) 511 | ti = argN++; 512 | 513 | std::pair sz; 514 | visit1_at(ti, [&](auto&& x) 515 | { _streamsize_or_not(x, sz); }, t); 516 | if (!sz.first) { 517 | out.setstate(os::failbit); 518 | return out; 519 | } 520 | if (sz.second >= 0) 521 | out.width(sz.second); 522 | else { 523 | // '-' clears '0' 524 | _ignore_zero_padding(); 525 | fl |= os::left; 526 | out.width(-sz.second); 527 | } 528 | } 529 | 530 | if (b == end(t)) { 531 | out.setstate(os::failbit); // incomplete spec 532 | return out; 533 | } 534 | 535 | // precision defaults to zero with a single '.' 536 | if (*b == out.widen('.')) { 537 | if (++b == end(t)) { 538 | out.setstate(os::failbit); 539 | return out; 540 | } 541 | if (*b == out.widen('*')) { 542 | ++b; 543 | int ti = _parse_position(b, end(t), fac); 544 | if (_bad_indexing(ti)) { 545 | out.setstate(os::failbit); 546 | return out; 547 | } 548 | if (ti == 0) 549 | ti = argN++; 550 | 551 | std::pair sz; 552 | visit1_at(ti, [&](auto&& x) 553 | { _streamsize_or_not(x, sz); }, t); 554 | if (!sz.first) { 555 | out.setstate(os::failbit); 556 | return out; 557 | } 558 | if (sz.second >= 0) 559 | pad.precision_ = sz.second; 560 | } else 561 | pad.precision_ = _parse_int(b, end(t), fac); 562 | } 563 | 564 | if (b == end(t)) { 565 | out.setstate(os::failbit); // incomplete spec 566 | return out; 567 | } 568 | 569 | // ignore all length modifiers 570 | switch (auto c = out.narrow(*b, 0)) { 571 | case 'h': case 'l': 572 | if (++b == end(t)) { 573 | out.setstate(os::failbit); 574 | return out; 575 | } 576 | c = out.narrow(*b, 0); 577 | if (c == 'h' or c == 'l') 578 | ++b; 579 | break; 580 | case 'j': case 'z': case 't': case 'L': 581 | ++b; 582 | break; 583 | } 584 | 585 | if (b == end(t)) { 586 | out.setstate(os::failbit); // incomplete spec 587 | return out; 588 | } 589 | 590 | // type-safe conversions are considered 591 | switch (out.narrow(*b, 0)) { 592 | case 'p': 593 | break; 594 | case 'X': 595 | fl |= os::uppercase; 596 | case 'x': 597 | fl |= os::hex; 598 | sp = spec::to_unsigned; 599 | if (pad.precision_ >= 0) 600 | _ignore_zero_padding(); 601 | break; 602 | case 'o': 603 | fl |= os::oct; 604 | sp = spec::to_unsigned; 605 | if (pad.precision_ >= 0) 606 | _ignore_zero_padding(); 607 | break; 608 | case 'E': 609 | fl |= os::uppercase; 610 | case 'e': 611 | fl |= os::scientific; 612 | break; 613 | case 'F': 614 | fl |= os::uppercase; 615 | case 'f': 616 | fl |= os::fixed; 617 | break; 618 | case 'G': 619 | fl |= os::uppercase; 620 | case 'g': /* floatfield == 0 */ 621 | break; 622 | case 'A': 623 | fl |= os::uppercase; 624 | case 'a': 625 | fl |= os::fixed | os::scientific; 626 | break; 627 | case 'u': 628 | fl |= os::dec; 629 | sp = spec::to_unsigned; 630 | if (pad.precision_ >= 0) 631 | _ignore_zero_padding(); 632 | break; 633 | case 'd': 634 | fl |= os::dec; 635 | case 'i': /* basefield == 0 */ 636 | sp = spec::to_int; 637 | if (pad.precision_ >= 0) 638 | _ignore_zero_padding(); 639 | break; 640 | case 's': 641 | fl |= os::boolalpha; 642 | break; 643 | case 'c': 644 | sp = spec::to_char; 645 | break; 646 | default: 647 | out.setstate(os::failbit); // bad format string 648 | return out; 649 | } 650 | 651 | switch (tolower(out.narrow(*b, 0))) { 652 | case 'p': case 's': case 'c': 653 | visit1_at(argN, [&](auto&& x) { 654 | typedef std::decay_t type_i; 655 | if (std::is_integral::value) 656 | pad.precision_ = -1; 657 | }, t); 658 | pad.align_sign_ = false; 659 | break; 660 | case 'e': case 'f': case 'g': 661 | if (pad.precision_ < 0) 662 | pad.precision_ = 6; 663 | case 'a': 664 | visit1_at(argN, [&](auto&& x) { 665 | typedef std::decay_t type_i; 666 | if (std::is_integral::value) 667 | pad.precision_ = -1; 668 | if (!std::is_floating_point::value) 669 | pad.align_sign_ = false; 670 | }, t); 671 | break; 672 | case 'd': case 'i': case 'u': case 'o': case 'x': 673 | visit1_at(argN, [&](auto&& x) { 674 | typedef std::decay_t type_i; 675 | if (!std::is_integral::value) 676 | pad.align_sign_ = false; 677 | }, t); 678 | } 679 | ++b; 680 | 681 | switch (sp) { 682 | case spec::none: 683 | visit1_at(argN, [&](auto x) { 684 | auto v = _output(out, x); 685 | pad.align_sign_ ? 686 | v.with_aligned_sign(fl, pad) : 687 | v.with(fl, pad); 688 | }, t); 689 | break; 690 | case spec::to_int: 691 | visit1_at(argN, [&](auto x) { 692 | auto v = _output(out, _to_int(x)); 693 | pad.align_sign_ ? 694 | v.with_aligned_sign(fl, pad) : 695 | v.with(fl, pad); 696 | }, t); 697 | break; 698 | case spec::to_unsigned: 699 | visit1_at(argN, [&](auto x) { 700 | _output(out, 701 | fl & os::dec ? 702 | _to_unsigned(_to_int(x)) : 703 | _to_unsigned(x)).with(fl, pad); 704 | }, t); 705 | break; 706 | case spec::to_char: 707 | visit1_at(argN, [&](auto x) { 708 | _output(out, 709 | _to_char(x)).with(fl, pad); 710 | }, t); 711 | break; 712 | } 713 | 714 | return from(t); 715 | } 716 | 717 | private: 718 | os& out; 719 | int argN; 720 | bool sequential; 721 | }; 722 | 723 | template 724 | struct _unformatted_guard { 725 | _unformatted_guard(Stream& s) : 726 | stream_(s), ok_(s), flags_(s.flags()), width_(s.width(0)) {} 727 | ~_unformatted_guard() { 728 | stream_.width(width_); 729 | stream_.flags(flags_); 730 | } 731 | 732 | explicit operator bool() const { 733 | return bool(ok_); 734 | } 735 | 736 | private: 737 | Stream& stream_; 738 | typename Stream::sentry ok_; 739 | typename Stream::fmtflags flags_; 740 | std::streamsize width_; 741 | }; 742 | 743 | template 744 | inline auto operator<<(std::basic_ostream& out, 745 | _fmt_put t) -> decltype(out) 746 | { 747 | _unformatted_guard> ok(out); 748 | 749 | if (ok) 750 | _put_fmt(out).from(t); 751 | return out; 752 | } 753 | 754 | } 755 | 756 | #endif 757 | --------------------------------------------------------------------------------