841 words
4 minutes
C++11上的简单format函数
std::format
是c++20下的功能,但是我们可能希望在C++11下就可以使用这项功能。如果不使用外部库的话,我们可以自己来写一个。
Description
- 只需要支持ascii字符,无需考虑宽字符。
- 对于每一个{},无需考虑arg-id的问题,即第一个{}就对应第一个参数,第二个{}对应第二个参数。
- 对于每一个{},内部的参数类型有限,只支持类似于
{:f}
,{:.2f}
,{:10.5f}
,{:8x}
,{:04}
,{:08x}
这一类的参数。即只考虑数字类型的参数,参数类型只有f
,d
,x
,s
或者无参数,但需要支持宽度和0
填充。 - 函数只可以使用C++11特性。不使用类似
sizeof...
之类的C++17特性。
Examples
fmt::format("Hello, {}!", "World") -> "Hello, World!" fmt::format("Hello integer, {}, {}, {}", 1, 2, 3) -> "Hello, 1, 2, 3" fmt::format("Hello float: {:.2f}", 3.1415926) -> "Hello float: 3.14" fmt::format("Hello string, integer, and float: {}, {:02x}, {:.2f}", "Hello", 1, 3.1415926) -> "Hello string, integer, and float: Hello, 01, 3.14" std::string world = "World"; fmt::format("Hello c++ string, {}!", world) -> "Hello, World!"
Prototype
namespace fmt { template<typename... Args> std::string format(const std::string& fmt, const Args&... args); };
Thinking
基本的思路是,扫描fmt
,对于,每一个{}
,如果里面没有参数,就把对应的输出参数直接调用输出流输出,如果有format参数,就解析format参数,然后再根据format格式进行输出。
另外,因为是在C++11下实现,没有sizeof...
运算符,也没有...
操作符,就只能递归调用模板函数。
Implementation
namespace fmt { template<typename T> std::string parse_format_arg(const std::string& fmt_arg, T& t) { bool fill_zero = false; int width = -1, precision = -1; char type = 'u'; // u means undefined, just print it if (fmt_arg.empty()) { return ""; } size_t pos = 0; // skip ":" if (fmt_arg[0] == ':') { pos ++; } // parse the fill character, notice that in here, we only allow '0' as fill character if (fmt_arg[pos] == '0') { fill_zero = true; pos ++; } // parse the width if (fmt_arg[pos] >= '1' && fmt_arg[pos] <= '9') { width = fmt_arg[pos] - '0'; pos ++; while (fmt_arg[pos] >= '0' && fmt_arg[pos] <= '9') { width = width * 10 + (fmt_arg[pos] - '0'); pos ++; } } // parse the precision if (fmt_arg[pos] == '.') { pos ++; if (fmt_arg[pos] >= '0' && fmt_arg[pos] <= '9') { precision = fmt_arg[pos] - '0'; pos ++; while (fmt_arg[pos] >= '0' && fmt_arg[pos] <= '9') { precision = precision * 10 + (fmt_arg[pos] - '0'); pos ++; } } } // parse the type, notice the argument may not have type switch (fmt_arg[pos]) { case 'd': case 'f': case 'x': case 's': type = fmt_arg[pos]; pos ++; case 'u': break; default: std::cerr << "{ERROR: FORMAT ARGUMENT TYPE NOT SUPPORTED!}"; exit(1); } // Number check switch (type) { case 'd': case 'f': case 'x': if (!std::is_integral<T>::value && !std::is_floating_point<T>::value) { std::cerr << "{ERROR: FORMAT ARGUMENT TYPE MISMATCH!}"; exit(1); } break; } std::ostringstream oss; if (width != -1) { oss << std::setw(width); } if (precision != -1) { oss << std::setprecision(precision); } if (fill_zero && type != 's') { oss << std::setfill('0'); } // change t according to the format argument switch (type) { case 'd': oss << std::dec; break; case 'f': oss << std::fixed; break; case 'x': oss << std::hex; break; } oss << t; return oss.str(); } // Return when sizeof...(Args) == 0 template<typename...Args> void format_core(std::stringstream& ss, const std::string& fmt, size_t pos) { if (pos < fmt.size()) { ss << fmt.substr(pos); } return ; } // format call this function recursively template<typename T, typename...Args> void format_core(std::stringstream& ss, const std::string& fmt, size_t pos, const T& t, const Args&... args) { size_t next_pos = fmt.find('{', pos); if (next_pos == std::string::npos) { ss << fmt.substr(pos); return; } ss << fmt.substr(pos, next_pos - pos); size_t end_pos = fmt.find('}', next_pos); if (end_pos == std::string::npos) { std::cerr << "{ERROR: NO MATCHING '}' IN FMT STRING!}"; exit(1); } std::string fmt_arg = fmt.substr(next_pos + 1, end_pos - next_pos - 1); fmt_arg.erase(0, fmt_arg.find_first_not_of(' ')); if (fmt_arg.empty()) { ss << t; } else { ss << parse_format_arg(fmt_arg, t); } format_core(ss, fmt, end_pos + 1, args...); } template<typename... Args> std::string format(const std::string& fmt, const Args&... args) { std::stringstream ss; format_core(ss, fmt, 0, args...); return ss.str(); } template<typename... Args> void print(const std::string& fmt, const Args&... args) { std::cout << format(fmt, args...); } template<typename... Args> void println(const std::string& fmt, const Args&... args) { std::cout << format(fmt, args...) << std::endl; } }
C++11上的简单format函数
https://ziyue.cafe/posts/simple-format-in-cpp11/