我有一个平安无事的下午,所以我想我可以尝试写一个字符串格式化程序。
这是基于我找到的这里文档。
#include <string>
#include <vector>
#include <map>
#include <ostream>
#include <iostream>
#include <exception>
#include <stdexcept>
#include <typeindex>
#include <cstdint>
#include <cstddef>
#include <cassert>
#include <iomanip>
template<typename... Args>
class Format
{
class Formatter
{
std::size_t used;
// Flags
enum class Length {none, hh, h, l, ll, j, z, t, L};
enum class Specifier {d, i, u, o, x, X, f, F, e, E, g, G, a, A, c, s, p, n};
bool leftJustify; // - Left-justify within the given field width; Right justification is the default (see width sub-specifier).
bool forceSign; // + Forces to preceed the result with a plus or minus sign (+ or -) even for positive numbers. By default, only negative numbers are preceded with a - sign.
bool forceSignWidth; // (space) If no sign is going to be written, a blank space is inserted before the value.
bool prefixType; // # Used with o, x or X specifiers the value is preceeded with 0, 0x or 0X respectively for values different than zero.
// Used with a, A, e, E, f, F, g or G it forces the written output to contain a decimal point even if no more digits follow. By default, if no digits follow, no decimal point is written.
bool leftPad; // 0 Left-pads the number with zeroes (0) instead of spaces when padding is specified (see width sub-specifier).
int width;
int precision;
Length length;
Specifier specifier;
std::type_info const* expectedType;
std::ios_base::fmtflags format;
public:
struct FormatterCheck
{
std::ostream& stream;
Formatter const& formatter;
FormatterCheck(std::ostream& s, Formatter const& formatter)
: stream(s)
, formatter(formatter)
{}
template<typename A>
std::ostream& operator<<(A const& nextArg)
{
formatter.apply(stream, nextArg);
return stream;
}
};
Formatter(char const* formatStr)
: used(0)
, leftJustify(false)
, forceSign(false)
, forceSignWidth(false)
, prefixType(false)
, leftPad(false)
, width(0)
, precision(6)
, length(Length::none)
, format(0)
{
char const* fmt = formatStr;
assert(*fmt == '%');
bool flag = true;
do {
++fmt;
switch(*fmt) {
case '-': leftJustify = true;break;
case '+': forceSign = true;break;
case ' ': forceSignWidth = true;break;
case '#': prefixType = true;break;
case '0': leftPad = true;break;
default: flag = false;
}
} while (flag);
if (std::isdigit(*fmt)) {
char* end;
width = std::strtol(fmt, &end, 10);
fmt = end;
}
if (*fmt == '.') {
++fmt;
if (std::isdigit(*fmt)) {
char* end;
precision = std::strtol(fmt, &end, 10);
fmt = end;
}
else {
precision = 0;
}
}
char first = *fmt;
++fmt;
switch(first) {
case 'h': length = Length::h;
if (*fmt == 'h') {
++fmt;
length = Length::hh;
}
break;
case 'l': length = Length::l;
if (*fmt == 'l') {
++fmt;
length = Length::ll;
}
break;
case 'j': length = Length::j;break;
case 'z': length = Length::z;break;
case 't': length = Length::t;break;
case 'L': length = Length::L;break;
default:
--fmt;
}
switch(*fmt) {
case 'd': specifier = Specifier::d;break;
case 'i': specifier = Specifier::i;break;
case 'u': specifier = Specifier::u;break;
case 'o': specifier = Specifier::o;break;
case 'x': specifier = Specifier::x;break;
case 'X': specifier = Specifier::X;break;
case 'f': specifier = Specifier::f;break;
case 'F': specifier = Specifier::F;break;
case 'e': specifier = Specifier::e;break;
case 'E': specifier = Specifier::E;break;
case 'g': specifier = Specifier::g;break;
case 'G': specifier = Specifier::G;break;
case 'a': specifier = Specifier::a;break;
case 'A': specifier = Specifier::A;break;
case 'c': specifier = Specifier::c;break;
case 's': specifier = Specifier::s;break;
case 'p': specifier = Specifier::p;break;
case 'n': specifier = Specifier::n;break;
default:
throw std::invalid_argument(std::string("Invalid Parameter specifier: ") + *fmt);
}
++fmt;
expectedType = getType(specifier, length);
used = fmt - formatStr;
format |= (leftJustify ? std::ios_base::left : std::ios_base::right);
if (specifier == Specifier::d || specifier == Specifier::i) {
format |= std::ios_base::dec;
}
else if (specifier == Specifier::o) {
format |= std::ios_base::oct;
}
else if (specifier == Specifier::x || specifier == Specifier::X) {
format |= std::ios_base::hex;
}
else if (specifier == Specifier::f || specifier == Specifier::F) {
format |= std::ios_base::fixed;
}
else if (specifier == Specifier::e || specifier == Specifier::E) {
format |= std::ios_base::scientific;
}
else if (specifier == Specifier::a || specifier == Specifier::A) {
format |= (std::ios_base::fixed | std::ios_base::scientific);
}
if (specifier == Specifier::X || specifier == Specifier::F || specifier == Specifier::E || specifier == Specifier::A || specifier == Specifier::G) {
format |= std::ios_base::uppercase;
}
if (prefixType && (specifier == Specifier::o || specifier == Specifier::x || specifier == Specifier::X)) {
format |= std::ios_base::showbase;
}
if (prefixType && (specifier == Specifier::a || specifier == Specifier::A || specifier == Specifier::e || specifier == Specifier::E || specifier == Specifier::f || specifier == Specifier::F || specifier == Specifier::g || specifier == Specifier::G)) {
format |= std::ios_base::showpoint;
}
if (forceSign && (specifier != Specifier::c && specifier != Specifier::s && specifier != Specifier::p)) {
format |= std::ios_base::showpos;
}
}
std::size_t size() const {return used;}
friend FormatterCheck operator<<(std::ostream& s, Formatter const& formatter)
{
return FormatterCheck(s, formatter);
}
private:
template<typename A>
void apply(std::ostream& s, A const& arg) const
{
if (std::type_index(*expectedType) != std::type_index(typeid(A))) {
throw std::invalid_argument(std::string("Actual argument does not match supplied argument: Expected(") + expectedType->name() + ") Got(" + typeid(A).name() + ")");
}
char fill = (!leftJustify && leftPad && (specifier != Specifier::c && specifier != Specifier::s && specifier != Specifier::p)) ? '0' : ' ';
int fillWidth = width;
if (forceSignWidth && !forceSign && (arg >= 0) && (specifier != Specifier::c && specifier != Specifier::s && specifier != Specifier::p)) {
s << ' ';
--fillWidth;
}
std::ios_base::fmtflags oldFlags = s.flags(format);
char oldFill = s.fill(fill);
int oldWidth = s.width(fillWidth);
std::streamsize oldPrec = s.precision(precision);
s << arg;
s.precision(oldPrec);
s.width(oldWidth);
s.fill(oldFill);
s.flags(oldFlags);
}
static std::type_info const* getType(Specifier specifier, Length length)
{
static std::map<std::pair<Specifier, Length>, std::type_info const*> typeMap = {
{{Specifier::d, Length::none}, &typeid(int)}, {{Specifier::i, Length::none}, &typeid(int)}, {{Specifier::d, Length::none}, &typeid(int*)},
{{Specifier::d, Length::hh}, &typeid(signed char)}, {{Specifier::i, Length::none}, &typeid(signed char)}, {{Specifier::d, Length::hh}, &typeid(signed char*)},
{{Specifier::d, Length::h}, &typeid(short int)}, {{Specifier::i, Length::none}, &typeid(short int)}, {{Specifier::d, Length::h}, &typeid(short int*)},
{{Specifier::d, Length::l}, &typeid(long int)}, {{Specifier::i, Length::none}, &typeid(long int)}, {{Specifier::d, Length::l}, &typeid(long int*)},
{{Specifier::d, Length::ll}, &typeid(long long int)}, {{Specifier::i, Length::none}, &typeid(long long int)}, {{Specifier::d, Length::ll}, &typeid(long long int*)},
{{Specifier::d, Length::j}, &typeid(std::intmax_t)}, {{Specifier::i, Length::none}, &typeid(std::intmax_t)}, {{Specifier::d, Length::j}, &typeid(std::intmax_t*)},
{{Specifier::d, Length::z}, &typeid(std::size_t)}, {{Specifier::i, Length::none}, &typeid(std::size_t)}, {{Specifier::d, Length::z}, &typeid(std::size_t*)},
{{Specifier::d, Length::t}, &typeid(std::ptrdiff_t)},{{Specifier::i, Length::none}, &typeid(std::ptrdiff_t)},{{Specifier::d, Length::t}, &typeid(std::ptrdiff_t*)},
{{Specifier::u, Length::none}, &typeid(unsigned int)}, {{Specifier::o, Length::none}, &typeid(unsigned int)}, {{Specifier::x, Length::none}, &typeid(unsigned int)}, {{Specifier::X, Length::none}, &typeid(unsigned int)},
{{Specifier::u, Length::hh}, &typeid(unsigned char)}, {{Specifier::o, Length::hh}, &typeid(unsigned char)}, {{Specifier::x, Length::hh}, &typeid(unsigned char)}, {{Specifier::X, Length::hh}, &typeid(unsigned char)},
{{Specifier::u, Length::h}, &typeid(unsigned short int)}, {{Specifier::o, Length::h}, &typeid(unsigned short int)}, {{Specifier::x, Length::h}, &typeid(unsigned short int)}, {{Specifier::X, Length::h}, &typeid(unsigned short int)},
{{Specifier::u, Length::l}, &typeid(unsigned long int)}, {{Specifier::o, Length::l}, &typeid(unsigned long int)}, {{Specifier::x, Length::l}, &typeid(unsigned long int)}, {{Specifier::X, Length::l}, &typeid(unsigned long int)},
{{Specifier::u, Length::ll}, &typeid(unsigned long long int)},{{Specifier::o, Length::ll}, &typeid(unsigned long long int)},{{Specifier::x, Length::ll}, &typeid(unsigned long long int)},{{Specifier::X, Length::ll}, &typeid(unsigned long long int)},
{{Specifier::u, Length::j}, &typeid(std::uintmax_t)}, {{Specifier::o, Length::j}, &typeid(std::uintmax_t)}, {{Specifier::x, Length::j}, &typeid(std::uintmax_t)}, {{Specifier::X, Length::j}, &typeid(std::uintmax_t)},
{{Specifier::u, Length::z}, &typeid(std::size_t)}, {{Specifier::o, Length::z}, &typeid(std::size_t)}, {{Specifier::x, Length::z}, &typeid(std::size_t)}, {{Specifier::X, Length::z}, &typeid(std::size_t)},
{{Specifier::u, Length::t}, &typeid(ptrdiff_t)}, {{Specifier::o, Length::t}, &typeid(ptrdiff_t)}, {{Specifier::x, Length::t}, &typeid(ptrdiff_t)}, {{Specifier::X, Length::t}, &typeid(ptrdiff_t)},
{{Specifier::f, Length::none}, &typeid(double)}, {{Specifier::F, Length::none}, &typeid(double)}, {{Specifier::f, Length::L}, &typeid(long double)}, {{Specifier::F, Length::L}, &typeid(long double)},
{{Specifier::e, Length::none}, &typeid(double)}, {{Specifier::E, Length::none}, &typeid(double)}, {{Specifier::e, Length::L}, &typeid(long double)}, {{Specifier::E, Length::L}, &typeid(long double)},
{{Specifier::g, Length::none}, &typeid(double)}, {{Specifier::G, Length::none}, &typeid(double)}, {{Specifier::g, Length::L}, &typeid(long double)}, {{Specifier::G, Length::L}, &typeid(long double)},
{{Specifier::a, Length::none}, &typeid(double)}, {{Specifier::A, Length::none}, &typeid(double)}, {{Specifier::a, Length::L}, &typeid(long double)}, {{Specifier::A, Length::L}, &typeid(long double)},
{{Specifier::c, Length::none}, &typeid(int)}, {{Specifier::c, Length::l}, &typeid(std::wint_t)},
{{Specifier::s, Length::none}, &typeid(char*)}, {{Specifier::c, Length::l}, &typeid(wchar_t*)},
{{Specifier::p, Length::none}, &typeid(void*)}
};
auto find = typeMap.find({specifier, length});
if (find == typeMap.end()) {
throw std::invalid_argument("Specifier and length are not a valid combination");
}
return find->second;
}
};
std::string format;
std::tuple<Args const&...> arguments;
std::vector<std::string> prefixString;
std::vector<Formatter> formater;
public:
Format(char const* fmt, Args const&... args)
: format(fmt)
, arguments(args...)
{
std::size_t count = sizeof...(args);
std::size_t pos = 0;
for(int loop = 0; loop < count; ++loop) {
// Not dealing with '\%' yet just trying to get it working.
std::size_t nextFormatter = format.find('%', pos);
if (nextFormatter == std::string::npos) {
throw std::invalid_argument("Invalid Format: not enough format specifiers for provided arguments");
}
prefixString.emplace_back(format.substr(pos, (nextFormatter - pos)));
formater.emplace_back(format.data() + nextFormatter);
pos = nextFormatter + formater.back().size();
}
std::size_t nextFormatter = format.find(pos, '%');
if (nextFormatter != std::string::npos) {
throw std::invalid_argument("Invalid Format: too many format specifiers for provided arguments");
}
prefixString.emplace_back(format.substr(pos, format.size() - pos));
}
void print(std::ostream& s) const
{
doPrint(s, std::make_index_sequence<sizeof...(Args)>());
}
private:
template<typename A>
struct Printer
{
Printer(std::ostream& s, std::string const& prefix, Formatter const& format, A const& value)
{
s << prefix << format << value;
}
};
template<typename A>
void forward(A const&...) const{}
template<std::size_t... I>
void doPrint(std::ostream& s, std::index_sequence<I...> const&) const
{
forward(1, Printer<decltype(std::get<I>(arguments))>(s, prefixString[I], formater[I], std::get<I>(arguments))...);
s << prefixString.back();
}
friend std::ostream& operator<<(std::ostream& s, Format const& format)
{
format.print(s);
return s;
}
};
template<typename... Args>
Format<Args...> make_format(char const* fmt, Args const&... args)
{
return Format<Args...>(fmt, args...);
}测试Harness
int main()
{
std::cout << make_format("Test One\n");
std::cout << make_format("Test Two %d\n", 12);
std::cout << make_format("Test Three %d %f\n", 12, 4.56);
std::cout << make_format("Test Four %d %d\n", 12, 4.56); // Should throw
}目前它并不是很有效率。除了代码审查之外,最好能了解如何使其更高效。也许我们可以在编译时而不是运行时解析字符串。
创建格式化程序对象时。它在字符串中找到'%‘格式规范。然后确保与参数相同数量的格式说明符。
当您打印"Format“对象时,它基本上强制对每个参数调用Formatter::apply()。这将在打印参数之前设置适当的标志,然后将其设置为原始值。格式化程序对象是在构造Format期间创建的,但在将对象发送到流之前不会应用。
发布于 2018-02-10 13:53:20
这可能是一个我不知道的特性,但它没有使用Visual 2015进行编译,因为A不是一个参数包:
template<typename A>
void forward(A const&...) const {}可能应该是:
template<typename... A>
void forward(A const&...) const {}(BUG:)这两个论点是错误的!
std::size_t nextFormatter = format.find(pos, '%');MSVC通过conversion from 'size_t' to 'char', possible loss of data警告捕捉到这一点。
在MSVC上产生警告/错误的其他小问题:
for (int loop = 0; loop < count; ++loop) { // signed / unsigned mismatch
...
int oldWidth = s.width(fillWidth); // conversion from 'std::streamsize' to 'int', possible loss of dataauto只在一个地方使用来保存类型,但它也可以极大地帮助防止像这样的意外转换,因为它迫使您只在必要时才考虑类型。(也就是说,上面的for循环要求您考虑类型,因为您没有从现有变量中获取类型。宽度声明不需要任何考虑:您需要函数返回值的类型)。
std::isdigit需要一个#include <cctype>。
考虑使用枚举和更多的描述性名称,而不是布尔语和注释。例如:
enum JustificationType { Left, Right };
JustificationType justification;
enum SignDisplayType { Always, OnlyPositive };
SignDisplayType signDisplay;
enum SignPaddingType { PadWhenNoSign, DontPad };
SignPaddingType signPadding;这会将选项的文档直接放在代码中。
发布于 2018-02-10 07:04:01
我们可以简化参数包,解压缩如下:
private:
// The template parameter is the index of the argument we are printing.
// Generated from the doPrint() function.
template<std::size_t I>
std::ostream& printValue(std::ostream& s) const
{
return s << prefixString[I] << formater[I] << std::get<I>(arguments);
}
// Called from print()
// The std::index_sequence<> is constructed based on the number of
// arguments passed to the constructor. This mean I is a sequence
// of numbers from 0 ... A (A is one less the number of param)
// We can use this to call printValue() A times using parameter
// pack expansion.
template<std::size_t... I>
void doPrint(std::ostream& s, std::index_sequence<I...> const&) const
{
std::ostream* ignore[] = {&printValue<I>(s)...};
s << prefixString.back();
} 这就消除了“减少美国”的功能:
template<typename A>
void forward(A const&...) const{}我们将struct Printer转换为函数调用printValue()。
template<typename A>
std::ostream& printValue(std::ostream& s,
std::string const& prefix,
Formatter const& format,
A const& value) const这意味着编译器可以进行参数类型推断。因此,呼叫可以简化如下:
// from
Printer<decltype(std::get<I>(arguments))>(s, prefixString[I], formater[I], std::get<I>(arguments))...
// too
printValue(s, prefixString[I], formater[I], std::get<I>(arguments))...但是现在它是一个函数(而不是一个类),它可以访问成员。所以我们实际上不需要传递所有这些参数。所以我们可以简化得更多:
// too
template<std::size_t I>
std::ostream& printValue(std::ostream& s) const
// Now the call is simply
printValue<I>(s)...发布于 2018-02-10 19:40:19
代码看起来很大,很吓人,也很难接近。我并没有深入研究它,但以下是我所看到的一瞥:
type_info。在不同的说明符上拆分、应用和使用std::is_same_v<T, U>是很好的。以下是我对这种格式的偏爱:
{x} -> here stands xth argument
other specifiers are inside {}, e.g. {x:4} -> fit in 4 chars它可能会很慢(虽然不是不合理的),但它的方便将很容易地平衡这一点。以下是我认为上述各点较佳的原因:
{x}放在其中。主要问题是在运行时获取第n个参数。很久以前,牦牛给了我一个素描。从那时起,我的模板元编程技能得到了提高,所以我决定自己编写。
template <std::size_t Index, typename Tuple>
void fallthrough(const Tuple& tup, std::ostream& os, std::size_t runtime_index)
{
if (runtime_index == Index)
{
os << std::get<Index>(tup);
return;
}
if constexpr (Index != 0)
{
return fallthrough<Index - 1>(tup, os, runtime_index);
}
throw "unreachable";
}其主要思想是继续下降,直到达到运行时索引为止。Fallthrough演示。但有趣的是,如果在编译时就知道索引,那么整个过程就会消失。此外,如果类型相同,它将衰变为跳转表。我确信编译器即使在类型不一样的情况下也可以生成跳转表,但我猜它知道一些我不知道的事情,反之亦然。
现在,在我们的掌握中,我们可以编写格式化程序:
#include <regex>
#include <cstddef>
#include <iosfwd>
#include <utility>
#include <string_view>
#include <string>
namespace details
{
template <typename InputIterator>
std::size_t to_size(InputIterator first, InputIterator last)
{
std::size_t size{0};
for (; first != last; ++first)
{
size += size * 10 + *first - '0';
}
return size;
}
template <std::size_t Index, typename Tuple>
void fallthrough(const Tuple& tup, std::ostream& os, std::size_t runtime_index)
{
if (runtime_index == Index)
{
os << std::get<Index>(tup);
return;
}
if constexpr (Index != 0)
{
return fallthrough<Index - 1>(tup, os, runtime_index);
}
throw "unreachable";
}
}
namespace nice_formatter
{
template <typename ... Args>
void format_nicely(std::ostream& os, const std::string& format, const Args& ... args)
{
auto tied = std::tie(args...);
std::regex arg_regex{"\\{\\d+\\}"};
std::sregex_iterator first_match{format.begin(), format.end(), arg_regex};
std::sregex_iterator last_match{};
auto last_pos = format.c_str();
while (first_match != last_match)
{
//output the stuff preceeding it
size_t match_index = first_match->position();
os << std::string_view{last_pos, static_cast<std::size_t>(&format[match_index] - last_pos)};
last_pos = &format[match_index] + first_match->length();
const char* argindex_str = &format[match_index];
//discard the { and } when passing to to_size
auto arg_index{details::to_size(argindex_str + 1, argindex_str + first_match->length() - 1)};
if (arg_index > sizeof...(Args))
{
throw std::invalid_argument{"Not enough arguments provided"};
}
details::fallthrough<sizeof...(Args) - 1>(tied,
os,
arg_index);
++first_match;
}
}
}(它里面可能有很多bug,我还没有对它进行太多的测试)
可以通过更改regex模式并在循环中添加更多的逻辑来添加说明符。
很小的例子:
#include <iostream>
int main()
{
std::size_t x = 0;
std::string some_text = "some text";
double y = 1.2;
nice_formatter::format_nicely(std::cout, "{0} {1} {2}\n", x, some_text, y);
nice_formatter::format_nicely(std::cout, "Duplicate of \"{0}\" is \"{0}\"", some_text);
}格式字符串通常是原始字符串文本,因此它是一个常量表达式。。也许可以编写一些类,以便能够在编译时标记格式字符串。
注意:请在本地机器上运行上述代码。对于编译器来说,这是非常复杂的,因为即使在上面的例子中,也需要花费大约一秒钟的时间。尝试在在线编译器上运行它可能需要相当长的时间。
https://codereview.stackexchange.com/questions/187230
复制相似问题