C++利用常量表达式在编译期操作字符串
2017-09-29 09:23
253 查看
在打log的时候,往往有这样的需求,要把当前代码文件的文件名打印出来。
最简单的就是输出
这样的输出太过冗长,我们需要的实际上只是
这个时候要是老老实实地调API把
如果在C++11之前,要在编译时做这个就得依赖模板元编程。牺牲可读性用满屏的
但是在C++11里,C++引入了名为
下面就是利用
可以看到上面的代码使用递归的方式找到最后一个分隔符(*nix下是
不过既然是递归,那么就有递归深度的问题。虽然代码是典型的尾递归,但是由于实现上的原因,编译器并不能对其进行尾递归优化。
对于递归深度的问题,常用编译器(GCC,Clang)的做法是限制递归深度(比如512层 ),也就是
当然有,但是需要利用C++14标准中扩充的constexpr。C++14允许在constexpr修饰的函数中使用for循环,你可能只需要在原本的运行时字符串分割函数前添加
上面的代码跟普通运行时的代码区别仅仅在于多了
不过需要注意的是,出于防止死循环导致编译时间无限长的考虑,部分编译器对编译期的for循环有次数限制,只是这个限制比递归大得多,比如GCC7.2限制在262144次。
还有就是,这个代码只能在支持C++14的编译器中编译通过,至少需要GCC5、VS2017或Clang3.4。
C++ compiler support
最简单的就是输出
__FILE__宏。但是
__FILE__实际上是包括文件名的完整路径,比如这样:
/tmp/blablabla-XXXX-YYYY-ZZZZZZ/example.cpp
这样的输出太过冗长,我们需要的实际上只是
example.cpp
这个时候要是老老实实地调API把
example.cpp切出来当然不难,但是想想,输入
__FILE__是编译时就确定了的,那么结果也应该可以在编译时确定啊,为什么要在运行时浪费时间去计算?
如果在C++11之前,要在编译时做这个就得依赖模板元编程。牺牲可读性用满屏的
template和
typename去实现这么一个简单的功能,总有种得不偿失的感觉。
但是在C++11里,C++引入了名为
constexpr(常量表达式)的特性,被
constexpr修饰的函数,如果满足一定条件,其返回值是可以在编译时计算出来的,在生成的汇编中将不会包含这个函数的任何代码。
下面就是利用
constexpr在编译时在完整路径中截取文件名的代码:
#include <cstdio> constexpr const char *get_basename(const char *filename, const int t) { if (t < 0) return filename; else if (filename[t] == '/' || filename[t] == '\\') return filename + t + 1; else return get_basename(filename, t - 1); } int main() { constexpr auto a = get_basename(__FILE__, sizeof(__FILE__) - 1); printf("%s", a); return 0; }
可以看到上面的代码使用递归的方式找到最后一个分隔符(*nix下是
/,Windows下是
\\)。
不过既然是递归,那么就有递归深度的问题。虽然代码是典型的尾递归,但是由于实现上的原因,编译器并不能对其进行尾递归优化。
对于递归深度的问题,常用编译器(GCC,Clang)的做法是限制递归深度(比如512层 ),也就是
__FILE__中的文件名不能太长,不然会出现编译错误,不过大多数情况下够用了。
Extended constexpr
上面的方法有递归深度的限制,那有没有更好的不需要递归的办法呢?当然有,但是需要利用C++14标准中扩充的constexpr。C++14允许在constexpr修饰的函数中使用for循环,你可能只需要在原本的运行时字符串分割函数前添加
constexpr,就可以实现编译期切割字符串。比如这样:
#include <cstdio> constexpr const char *get_basename(const char *filename, const int t) { for (int i = t; i >= 0; i--) { if (filename[i] == '/' || filename[i] == '\\') return filename + i + 1; } return filename; } int main() { constexpr auto a = get_basename(__FILE__, sizeof(__FILE__) - 1); printf("%s", a); return 0; }
上面的代码跟普通运行时的代码区别仅仅在于多了
constexpr关键字修饰。
不过需要注意的是,出于防止死循环导致编译时间无限长的考虑,部分编译器对编译期的for循环有次数限制,只是这个限制比递归大得多,比如GCC7.2限制在262144次。
还有就是,这个代码只能在支持C++14的编译器中编译通过,至少需要GCC5、VS2017或Clang3.4。
参考文献
Can constexpr function evaluation do tail recursion optimizationC++ compiler support
相关文章推荐
- C++中使用正规表达式操作字符串
- C++中的数组array和vector,lambda表达式,C字符串加操作,C++中新类型数组(数组缓存),多元数组,new缓冲
- [C++ 学习] C++ Primer 第4版 习题 4.30 字符串操作 & 我对堆内存 和 常量区内存的一些理解
- C++中的数组array和vector,lambda表达式,C字符串加操作,C++中新类型数组(数组缓存),多元数组,new缓冲
- note : OD操作整理-修改常量字符串;保存PE文件
- C#利用正则表达式实现字符串搜索
- C#中利用正则表达式实现字符串搜索(摘自天涯人生)
- C/C++--字符串拷贝及内存操作(转载)
- struts2中用OGNL表达式定义字符串常量与单个字符常量需要注意的一个小细节
- C#中利用正则表达式实现字符串搜索
- 《利用Python进行数据分析》第7章 字符串操作与正则表达式
- Java利用正则表达式统计某个字符串出现的次数
- c++常用字符串操作函数
- 利用正则表达式 替换字符串中多个 URL
- 字符与字符串操作——Windows via C/C++
- 1 通过JNI混合使用Java和C++ -----> 操作字符串
- C++第八周mooc在线测评—第8周 按址操作(2)——指针与数组、字符串、结构体,动态数组
- 《Windows Via C/C++》边学习,边翻译(七)操作字符和字符串-6
- 关于C++的字符串操作
- C#中利用正则表达式实现字符串搜索