// Boost.TypeErasure library // // Copyright 2012 Steven Watanabe // // Distributed under the Boost Software License Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // $Id$ //[printf /*` (For the source of this example see [@boost:/libs/type_erasure/example/printf.cpp printf.cpp]) This example uses the library to implement a type safe printf. [note This example uses C++11 features. You'll need a recent compiler for it to work.] */ #include #include #include #include #include #include #include #include #include #include #include namespace mpl = boost::mpl; using namespace boost::type_erasure; using namespace boost::io; // We capture the arguments by reference and require nothing // except that each one must provide a stream insertion operator. typedef any< mpl::vector< typeid_<>, ostreamable<> >, const _self& > any_printable; typedef std::vector print_storage; // Forward declaration of the implementation function void print_impl(std::ostream& os, const char * format, const print_storage& args); // print // // Writes values to a stream like the classic C printf function. The // arguments are formatted based on specifiers in the format string, // which match the pattern: // // '%' [ argument-number '$' ] flags * [ width ] [ '.' precision ] [ type-code ] format-specifier // // Other characters in the format string are written to the stream unchanged. // In addition the sequence, "%%" can be used to print a literal '%' character. // Each component is explained in detail below // // argument-number: // The value must be between 1 and sizeof... T. It indicates the // index of the argument to be formatted. If no index is specified // the arguments will be processed sequentially. If an index is // specified for one argument, then it must be specified for every argument. // // flags: // Consists of zero or more of the following: // '-': Left justify the argument // '+': Print a plus sign for positive integers // '0': Use leading 0's to pad instead of filling with spaces. // ' ': If the value doesn't begin with a sign, prepend a space // '#': Print 0x or 0 for hexadecimal and octal numbers. // // width: // Indicates the minimum width to print. This can be either // an integer or a '*'. an asterisk means to read the next // argument (which must have type int) as the width. // // precision: // For numeric arguments, indicates the number of digits to print. For // strings (%s) the precision indicates the maximum number of characters // to print. Longer strings will be truncated. As with width // this can be either an integer or a '*'. an asterisk means // to read the next argument (which must have type int) as // the width. If both the width and the precision are specified // as '*', the width is read first. // // type-code: // This is ignored, but provided for compatibility with C printf. // // format-specifier: // Must be one of the following characters: // d, i, u: The argument is formatted as a decimal integer // o: The argument is formatted as an octal integer // x, X: The argument is formatted as a hexadecimal integer // p: The argument is formatted as a pointer // f: The argument is formatted as a fixed point decimal // e, E: The argument is formatted in exponential notation // g, G: The argument is formatted as either fixed point or using // scientific notation depending on its magnitude // c: The argument is formatted as a character // s: The argument is formatted as a string // template void print(std::ostream& os, const char * format, const T&... t) { // capture the arguments print_storage args = { any_printable(t)... }; // and forward to the real implementation print_impl(os, format, args); } // This overload of print with no explicit stream writes to std::cout. template void print(const char * format, const T&... t) { print(std::cout, format, t...); } // The implementation from here on can be separately compiled. // utility function to parse an integer int parse_int(const char *& format) { int result = 0; while(char ch = *format) { switch(ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': result = result * 10 + (ch - '0'); break; default: return result; } ++format; } return result; } // printf implementation void print_impl(std::ostream& os, const char * format, const print_storage& args) { int idx = 0; ios_flags_saver savef_outer(os, std::ios_base::dec); bool has_positional = false; bool has_indexed = false; while(char ch = *format++) { if (ch == '%') { if (*format == '%') { os << '%'; continue; } ios_flags_saver savef(os); ios_precision_saver savep(os); ios_fill_saver savefill(os); int precision = 0; bool pad_space = false; bool pad_zero = false; // parse argument index if (*format != '0') { int i = parse_int(format); if (i != 0) { if(*format == '$') { idx = i - 1; has_indexed = true; ++format; } else { os << std::setw(i); has_positional = true; goto parse_precision; } } else { has_positional = true; } } else { has_positional = true; } // Parse format modifiers while((ch = *format)) { switch(ch) { case '-': os << std::left; break; case '+': os << std::showpos; break; case '0': pad_zero = true; break; case ' ': pad_space = true; break; case '#': os << std::showpoint << std::showbase; break; default: goto parse_width; } ++format; } parse_width: int width; if (*format == '*') { ++format; width = any_cast(args.at(idx++)); } else { width = parse_int(format); } os << std::setw(width); parse_precision: if (*format == '.') { ++format; if (*format == '*') { ++format; precision = any_cast(args.at(idx++)); } else { precision = parse_int(format); } os << std::setprecision(precision); } // parse (and ignore) the type modifier switch(*format) { case 'h': ++format; if(*format == 'h') ++format; break; case 'l': ++format; if(*format == 'l') ++format; break; case 'j': case 'L': case 'q': case 't': case 'z': ++format; break; } std::size_t truncate = 0; // parse the format code switch(*format++) { case 'd': case 'i': case 'u': os << std::dec; break; case 'o': os << std::oct; break; case 'p': case 'x': os << std::hex; break; case 'X': os << std::uppercase << std::hex; break; case 'f': os << std::fixed; break; case 'e': os << std::scientific; break; case 'E': os << std::uppercase << std::scientific; break; case 'g': break; case 'G': os << std::uppercase; break; case 'c': case 'C': break; case 's': case 'S': truncate = precision; os << std::setprecision(6); break; default: assert(!"Bad format string"); } if (pad_zero && !(os.flags() & std::ios_base::left)) { os << std::setfill('0') << std::internal; pad_space = false; } if (truncate != 0 || pad_space) { // These can't be handled by std::setw. Write to a stringstream and // pad/truncate manually. std::ostringstream oss; oss.copyfmt(os); oss << args.at(idx++); std::string data = oss.str(); if (pad_space) { if (data.empty() || (data[0] != '+' && data[0] != '-' && data[0] != ' ')) { os << ' '; } } if (truncate != 0 && data.size() > truncate) { data.resize(truncate); } os << data; } else { os << args.at(idx++); } // we can't have both positional and indexed arguments in // the format string. assert(has_positional ^ has_indexed); } else { std::cout << ch; } } } int main() { print("int: %d\n", 10); print("int: %0#8X\n", 0xA56E); print("double: %g\n", 3.14159265358979323846); print("double: %f\n", 3.14159265358979323846); print("double: %+20.9e\n", 3.14159265358979323846); print("double: %0+20.9g\n", 3.14159265358979323846); print("double: %*.*g\n", 20, 5, 3.14159265358979323846); print("string: %.10s\n", "Hello World!"); print("double: %2$*.*g int: %1$d\n", 10, 20, 5, 3.14159265358979323846); } //]