C++ 20 的 std::format 是一个很神奇、很实用的工具,最神奇的地方在于它能在编译期检查字符串的格式是否正确,而且不需要什么特殊的使用方法,只需要像使用普通函数那样传参即可。

#include <format>

int a = 1;
std::string s1 = std::format("a: {}", a); // OK
std::string s2 = std::format("a: {}, b: {}", a); // 编译错误

C++ 20 的 std::format 来自一个著名的开源库 {fmt}。在 C++ 20 之前,fmt 需要为每个字符串字面量创建不同的类型才能实现编译期格式检查。fmt 提供了一个 FMT_STRING 宏以简化使用的流程。

#include <fmt/format.h>

int a = 1;
std::string s1 = fmt::format(FMT_STRING("a: {}"), a); // OK
std::string s2 = fmt::format(FMT_STRING("a: {}, b: {}"), a); // 编译错误

C++ 20 有了 consteval 后就不用这么别扭了。consteval 函数与以前的 constexpr 函数不同,constexpr 函数只有在必须编译期求值的语境下才会在编译期执行函数,而 consteval 函数在任何情况下都强制编译期求值。std::format 就是利用 consteval 函数在编译期执行代码,来检查字符串参数的格式。

然而 std::format 自身不能是 consteval 函数,只好曲线救国,引入一个辅助类型 std::format_string,让字符串实参隐式转换为 std::format_string。只要这个转换函数是 consteval 函数,并且把格式检查的逻辑写在这个转换函数里面,照样能实现编译期的格式检查。

这里我们实现了一个极简版的 format,可以检查字符串中 {} 的数量是否与参数的个数相同。format_string 的构造函数就是我们需要的隐式转换函数,它是一个 consteval 函数。若字符串中 {} 的数量不对,则代码会执行到 throw 这一行。C++ 的 throw 语句不能在编译期求值,因此会引发编译错误,从而实现了在编译期检查出字符串的格式错误。

namespace my {
template<class ...Args>
class format_string {
private:
std::string_view str; public:
template<class T>
requires std::convertible_to<const T &, std::string_view>
consteval format_string(const T &s)
: str(s)
{
std::size_t actual_num = 0;
for (std::size_t i = 0; i + 1 < str.length(); i++) {
if (str[i] == '{' && str[i + 1] == '}') {
actual_num++;
}
}
constexpr std::size_t expected_num = sizeof...(Args);
if (actual_num != expected_num) {
throw std::format_error("incorrect format string");
}
} std::string_view get() const { return str; }
}; template<class ...Args>
std::string format(format_string<std::type_identity_t<Args>...> fmt, Args &&...args) {
// 省略具体的格式化逻辑
}
}

有一个细节,此处 format 函数的参数写的是 format_string<std::type_identity_t<Args>...>,直接写 format_string<Args ...> 是无法隐式转换的,因为模板实参推导 (template argument deduction) 不会考虑隐式转换,C++ 20 提供了一个工具 std::type_identity 可以解决这个问题。std::type_identity 其实就是一个关于类型的恒等函数,但是这么倒腾一下就能在模板实参推导中建立非推导语境 (non-deduced context),进而正常地匹配到隐式转换,C++ 就是这么奇怪。参考资料:c++ - why would type_identity make a difference? - Stack Overflow

std::format 如何实现编译期格式检查的更多相关文章

  1. 编译期类型检查 in ClojureScript

    前言  话说"动态类型一时爽,代码重构火葬场",虽然有很多不同的意见(请参考),但我们看到势头强劲的TypeScript和Flow.js,也能感知到静态类型在某程度上能帮助我们写出 ...

  2. C++17尝鲜:编译期 if 语句

    Constexpr If(编译期 if 语句) 以 if constexpr 打头的 if 语句被称为 Constexpr If. Constexpr If 是C++17所引入的新的语法特性.它为C+ ...

  3. 通过宏封装实现std::format编译期检查参数数量是否一致

    背景 std::format在传参数量少于格式串所需参数数量时,会抛出异常.而在大部分的应用场景下,参数数量不一致提供编译报错更加合适,可以促进我们更早发现问题并进行改正. 最终效果 // 测试输出接 ...

  4. Javac早期(编译期)

    从Sun Javac的代码来看,编译过程大致可以分为3个过程: 解析与填充符号表过程. 插入式注解处理器的注解处理过程. 分析与字节码生成过程. Javac编译动作的入口是com.sun.tools. ...

  5. 读书笔记 effective c++ Item 41 理解隐式接口和编译期多态

    1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), class Widget { public: Widget(); virtual ~W ...

  6. 嵌入式C语言自我修养 08:变参函数的格式检查

    8.1 属性声明:format GNU 通过 __atttribute__ 扩展的 format 属性,用来指定变参函数的参数格式检查. 它的使用方法如下: __attribute__(( forma ...

  7. c++ 编译期与运行期

    分享到 一键分享 QQ空间 新浪微博 百度云收藏 人人网 腾讯微博 百度相册 开心网 腾讯朋友 百度贴吧 豆瓣网 搜狐微博 百度新首页 QQ好友 和讯微博 更多... 百度分享 转自:http://h ...

  8. 《深入理解Java虚拟机》-----第10章 程序编译与代码优化-早期(编译期)优化

    概述 Java语言的“编译期”其实是一段“不确定”的操作过程,因为它可能是指一个前端编译器(其实叫“编译器的前端”更准确一些)把*.java文件转变成*.class文件的过程;也可能是指虚拟机的后端运 ...

  9. Java编译期与运行期

    编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程.在Java中也就是把Java代码编成class文件的过程.编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本 ...

  10. java编译期优化

    java语言的编译期其实是一段不确定的操作过程,因为它可以分为三类编译过程: 1.前端编译:把.java文件转变为.class文件 2.后端编译:把字节码转变为机器码 3.静态提前编译:直接把*.ja ...

随机推荐

  1. day05---系统的重要文件(3)

    1) /usr/local 编辑 安装的软件 第三方软件安装位置 软件安装的三种方法 1.yum安装 自动解决依赖问题 yum [选项参数] 包名 第一个里程碑:我想要安装的软件的名字 或者是 知道命 ...

  2. 第130篇:BOM(window对象)

    好家伙,本篇为<JS高级程序设计>第十二章"BOM"学习笔记   什么是BOM? BOM(Browser Object Model) 是指浏览器对象模型,是用于描述这种 ...

  3. 【Azure 应用服务】App Service 通过 wardeploy 部署 war 包,如何指定到 root目录为wwwroot

    问题描述 在部署War包到App Service时,参考文档:(使用 ZIP 或 WAR 文件将应用部署到 Azure 应用服务 : https://docs.azure.cn/zh-cn/app-s ...

  4. 万字长文学会对接 AI 模型:Semantic Kernel 和 Kernel Memory,工良出品,超简单的教程

    万字长文学会对接 AI 模型:Semantic Kernel 和 Kernel Memory,工良出品,超简单的教程 目录 万字长文学会对接 AI 模型:Semantic Kernel 和 Kerne ...

  5. Vue3学习(二十一)- 文档管理页面布局修改

    写在前面 按照国际惯例,要先聊下生活,吐槽一番,今天是2月14日,也是下午听老妈说,我才知道! 现在真的是对日期节日已经毫无概念可言,只知道星期几. 现在已经觉得写博客也好,学习文章也罢,和写日记一样 ...

  6. centos7挂载硬盘(大于2T)

    配置方法: 1.root账户下,执行 fdisk -l 命令查看挂载的硬盘设备,假设设备号为/dev/sdb,接下来我们使用parted命令来进行GPT分区 2.使用parted命令进行GPT分区 # ...

  7. Metasploitable3 渗透测试

    1.信息手机阶段 信息收集经常使用的软件 功能也比较强大的Nmap Nmap nmap -p- -sS -sV -n -v --reason --open -oX demon.xml 192.168. ...

  8. vue和xml复习

    复习 JS知识梳理 JS定义的位置 行内js(事件名="javascript:js代码"),内部js(

  9. iview select 下拉 多选 数组 外面包一层 数组改逗分,外层不能用v-model 要用 :value @input,input里面要把对象解构下,才能过验证 - vue

    iview select 下拉 多选 数组 外面包一层 数组改逗分,外层不能用v-model 要用 :value @input,input里面要把对象解构下,才能过验证 - vue

  10. arch安装xfce4的时候,出现无法设置开机启动的问题

    sudo systemctl enable lightdm   Failed to enable unit: File /etc/systemd/system/display-manager.serv ...