现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34199 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

39 | 如何在编译期玩转字符串?

使用宏定义统一 GCC 扩展和 C++20 写法
字符串字面量运算符模板
使用 compile_time_string 结构
operator""_cts()
使用字符串字面量作为模板参数
定义宏 CARG, CARG_WRAP, CARG_UNWRAP
使用 lambda 表达式封装编译期参数 (Michael Park 方法)
使用模板参数传递
array 实现 substr
string_view.substr (不总是足够)
string_view 直接比较
string_view.find()
char_traits.find()
string_view.size()
标准库 char_traits
自实现 constexpr length
[5] cppreference.com, “User-defined literals”
[4] cppreference.com, “Template parameters and template arguments”
[3] Richard Smith, “Literal operator templates for strings”
[2] Michael Park, “constexpr function parameters”
[1] David Stone, “constexpr function parameters”
尝试在编译期去掉文件名前的路径部分
利用技巧在编译期而非运行期处理字符串
讨论了编译期处理字符串的技巧
接口统一
C++20 标准方法
GCC 扩展
解决方案
函数参数不是编译期常量
截取子串
字符串比较
查找字符
获取字符串长度
array
string_view (C++17)
常字符指针
参考资料
课后思考
内容小结
字符串模板参数
编译期传参问题
常见操作
对象的选择
编译期字符串处理技巧

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
在实际的项目里,我遇到过不少在编译期处理字符串的需求。今天,我们就来把这个问题好好讨论一下。

对象的选择

在编译期处理字符串,你是不能使用 std::string 的。原因有以下几个方面:
在 C++20 之前你完全无法在编译期使用 string。而且,对于编译期 string 的支持来得比较晚,只有 MSVC 较早支持,GCC 需要刚出炉不久的 GCC 12,Clang 需要当前(2022 年 6 月)尚未正式发布的 LLVM 15。
到了 C++20,你虽然可以在编译期使用 string,但实际上仍有很多不方便的地方。最明显的,编译期生成的字符串不能在运行期使用。并且,string 不可以声明为 constexpr。
string 不能用作模板参数。
因此我们只能放弃这个看起来最方便的方式,另外探索一条新路。我们的基本操作对象可以是下面这几样:
常字符指针,这是字符串字面量会自然退化成的东西
string_view,C++17 里新增的有力工具,方法和 string 类似,且基本都是 constexpr
array,使用它我们才可以返回全新的字符串
我们的编译期字符串处理,也因此会围绕着这几种类型来进行讨论。

常见操作

获取字符串长度

一个最最基本的操作,显然就是获取字符串的长度。这里,我们不能使用 C 的 strlen 函数,因为这个函数不是 constexpr。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了在编译期处理字符串的方法和技巧,重点介绍了使用常字符指针、`string_view`和`array`的方法。作者详细讨论了获取字符串长度、查找字符、字符串比较和截取子串等常见操作的实现方式。此外,还分享了实际项目中遇到的问题和解决方案,以及不同版本编译器对代码生成的影响。文章还介绍了在参数传递中将字符串作为类型使用的方法,包括GCC的非标准扩展和C++20的标准方法。最后,作者提出了使用宏定义统一接口的建议,以便在实际项目中使用这些功能的代码是统一的。总的来说,本文对编译期字符串处理的方法和技巧进行了深入浅出的介绍,对于需要在编译期处理字符串的读者具有一定的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(4)

  • 最新
  • 精选
  • 李云龙
    分享一下我的思考题解决方案:我原本想着用递归来写,但编译一直没有通过。最后我只能用手动展开的方法,写了一个最多支持5层目录的路径前缀分离,除了下面这个函数之外,其他函数都与老师的保持一致。 template <CARG Str> constexpr auto remove_head_5(Str cpath) { constexpr auto path = CARG_UNWRAP(cpath); constexpr int skip = (*path == '/') ? 1 : 0; constexpr auto pos = path + skip; constexpr auto start = find(pos, '/'); if constexpr (start == nullptr) { return copy_str<length(pos)>(pos); } else { constexpr auto pos2 = start + 1; constexpr auto start2 = find(pos2, '/'); if constexpr (start2 == nullptr) { return copy_str<length(pos2)>(pos2); } else { constexpr auto pos3 = start2 + 1; constexpr auto start3 = find(pos3, '/'); if constexpr (start3 == nullptr) { return copy_str<length(pos3)>(pos2); } else { constexpr auto pos4 = start3 + 1; constexpr auto start4 = find(pos4, '/'); if constexpr (start4 == nullptr) { return copy_str<length(pos4)>(pos4); } else { return copy_str<length(start4 + 1)>(start4 + 1); } } } } } int main() { //调用 puts(::remove_head_5(CARG_WRAP("/usr/local/test/txt/file")).data()); return 0; }

    作者回复: 有限制还是不好。实现一个 rfind(需要给出长度,或者调用编译期可执行的 strlen)或者 find_last_of 就可以了。原理上没有特别之处。

    2023-11-20归属地:北京
    2
    1
  • 李云龙
    老师,这段代码的两个return返回不同的array类型,但可以编译通过,是因为pos和start已经是编译期常量了,在编译期就可以确定走哪个return分支,从而就可以确定函数的返回值类型了。我的理解正确吗? if constexpr (start == nullptr) { return copy_str<length(pos)>(pos); } else { return copy_str<length(start + 1)>(start + 1); }

    作者回复: 对,如果 start 是普通的函数参数那就不行了。

    2023-11-19归属地:美国
    1
  • 行大运
    什么时候出一个完整的C++20专栏!

    作者回复: 目前没有计划……

    2023-01-24归属地:广东
  • piboye
    c++20 字符串处理,可以超越c了

    作者回复: C++ 字符串处理有哪里不如 C?

    2022-09-06归属地:上海
    4
收起评论
大纲
固定大纲
对象的选择
常见操作
获取字符串长度
显示
设置
留言
4
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部