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. virtualenvwrapper管理虚拟环境

    安装 pip install virtualenvwrapper-win 基本使用 1.创建虚拟环境 mkvirtualenv my_env 使用这个命令,就会在你c盘的当前用户下创建一个Env的文件 ...

  2. iOS 面向对象与类

    至于未来会怎样,要走下去才知道反正路还很长,天总会亮. 1. 面向对象 1.1 什么是面向对象(OOP) 面向对象 Object Oriented Programming.在软件开发中,我们虽然用的是 ...

  3. 游戏H5引擎Canvas屏幕自适应CSS代码

    canvas.style = `touch-action: none; width:${ width }px; height:${ height }px; cursor: inherit;`;

  4. 【Azure 应用服务】App Service中抓取 Web Job 的 DUMP 办法

    问题描述 使用Azure App Service,也可以部署一个Java程序作为Web Job运行.运行一个 .Jar 文件只需要以下4步: 1)把Java应用打包成一个 .jar 文件 2)创建一个 ...

  5. Android\C++\C#\Java

    关于:(38条消息) 千万不能错过的Android NDK下载安装及配置_石子君的博客-CSDN博客_android ndk下载 (38条消息) Android扩展知识 - so文件生成及其使用_L- ...

  6. Java interface 接口的使用 implements 实现----

    1 package com.bytezreo.interfacetest; 2 3 /** 4 * 5 * @Description interface 接口的使用 implements 实现---- ...

  7.  liunx上安装django ,启动uwsgi ,语音播报python实现过程

    由于需要做一个语音播报实现,用到的技术是python  ,需要事先搭环境,安装uwsgi  djagno环境,以下内容为百度上找到的好一点的内容,确实照着做成功了,转载一下,下次更好找资料 liunx ...

  8. 闭关修炼180天 -- 手写SpringMVC框架(迷你版)

    SpringMvc知识须知 MVC设计模式 Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业 务. View(视图): 通常指的就是我们的 jsp 或者 htm ...

  9. MySQL之过滤条件

    [一]筛选过滤条件 [1]查询语句 -- 查询当前表中的指定字段的数据 select id,name from emp where id > 3; [2]创建数据表 create databas ...

  10. TimeLine 时间轴 网站 分享 time.graphics - nodejs 10年图

    话说20年 弹指一挥间 https://time.graphics/line/682014 nodejs 10年图 https://time.graphics/line/598790 资料 2022, ...