基于C++可变参数模板格式化字符串

遨游于C++世界时,最讨厌的当属于对c-style的兼容 。

在格式化字符串时,通常使用的是 snprintf 这个c函数。 snprintf 是 sprintf 的安全版,能够避免缓冲区溢出。

charbuf[1024] = {0};std::strings ="Hello world";snprintf(buf,sizeof(buf),"format str: %s", s.c_str());

snprintf 接受的参数跟 printf 差不多,都是c-style的数据类型,如 %s 接受的是 const char* 类型的数据,这就需要我们将 std::string 做一个转换。这一个小小的转换让我觉得非常不爽,有没有可能让 std::string 在做某个函数的参数时能自动做转换?甚至让一个将一个普通的对象自动转换成 const char* 类型?

接下来我们将利用c++11的可变参数模板(variadic templates)、参数包(parameters pack)、完美转发(perfect forwarding)等特性来实现这一想法。

参数列表完美转发

写C++代码时,我满脑子都是怎么最大限度地提高性能。我们这次的目标也一样,在提供方便的同时,不要对性能有太大影响,甚至不影响。

首先是要将传入 fmt 函数的参数完美转发至 snprintf 。

templatestringfmt(constchar*format, Args&&... args){charbuf[128] = {0};snprintf(buf,sizeof(buf), format, convert(std::forward(args))...);returnbuf;}

这是一个可变参数模板,args表示传入的参数们。我们的思路是将传入的参数做一个转换之后传给 snprintf 。

为了原封不动的保持左右值引用,首先是用 Args&& 代替 Args 的参数类型,此处模板函数的Args需要编译器推导,所以是一个通用引用(Universal reference),可以指代左值或右值。用 std::forward 能保持参数的左右值性质,做到参数的完美转发。

自动参数转换

在 convert 这个函数中,我们要将特定的类型转换成 const char* 类型,而那些能被 snprintf 接受的类型如 int , double , char* ,则原封不动的返回。

convert 函数针对不同的参数类型需要返回不同的类型。这里也将返回值作为一个模板类型即可。

templatestructitem_return {usingtype = T&&;};

convert 函数的定义为:

templateinlinetypenameitem_return::typeconvert(T&& arg){returnstatic_cast(arg);}

convert函数默认将传入的参数原封不动的返回。接下来我们要做模板的偏特化,对于指定的对象,将其转换为const char *类型

// lvaluetemplate<>structitem_return {usingtype =constchar*;};template<>inlinetypenameitem_return::type convert(obj &arg) {std::cout<<"receive lvalue\n";returnarg.s.c_str();}// rvaluetemplate<>structitem_return {usingtype =constchar*;};template<>inlinetypenameitem_return::type convert(obj &&arg) {std::cout<<"receive rvalue\n";returnarg.s.c_str();}

注意,返回值也是需要偏特化的。

最后

我构造了一个class,hook他的两个构造函数以便于观察是否发生了拷贝。

classobj {public:strings;  obj(constchar* ss) {    s = ss;  }  obj(constobj& other):s(other.s) {printf("copy constructor\n");  }  obj(obj&& other):s(other.s) {printf("move constructor\n");    other.s.clear();  }};

之后我们使用fmt函数,就能像格式化c-style字符串一样,格式化任意一个对象啦。

intmain(){obja("haha");intb =3;std::cout<< fmt("%s %s\n%d %d", a, obj("xixi"), b,2) <

运行结果为

receivelvaluereceivervaluehaha xixi32

很好,并没有发生拷贝。

如果有想学习c++的程序员,可来我们的C/C++学习扣qun:589348389,

免费送C++的视频教程噢!

我每晚上8点还会在群内直播讲解C/C++知识,欢迎大家前来学习哦。

你可能感兴趣的:(基于C++可变参数模板格式化字符串)