寻求安全文本格式化API

2021-06-20 06:46:37

由于在50年代在Fortran中引入格式字符串,因此在其文本格式和I / O API中使用它们的所有allmmajor编程语言:

一个值得注意的例外是C ++ iOStreams,它使用运算符重载andper-Stream状态操作来控制格式。此时,状态apishave几乎被证明是在可用性和表演方面的失败,许多C ++程序员更喜欢* printf而不是。

但是,C中的格式字符串具有糟糕的声誉,因为缺乏类型:用户必须将类型信息与格式信息一起编码。如果用户指定的和实际类型不匹配,我们有一个undefinedbehavior(govebolt):

这将指针的一部分打印为十进制数,在其他情况下,您可以geta segfault或更糟糕的事情。

幸运的是,现代编译器可以在文字格式字符串的编译时诊断此类错误,但这是一个不仅是理想的选择,不仅可以易于出错,而且来自C STDINT的文档也是笨重的ASTHIS表。 h头示出了:

除C之外的语言的格式化设施通常是suffice-safe。例如,在Python中,在尝试格式化字符串asan整数时,您将获得异常:

>>> ' {:d}' 。格式("我不是数字")回溯(最近呼叫最后一个电话):文件"< stdin>"第1行,在<模块> ValueError:未知格式代码' D'对于类型' str'

此外,格式说明符只传达格式信息,而不是打字机使语法更简单,更容易解析,消除了无数少的说明符。例如,D意味着像十进制一样格式,而不是TheType是int,它应该将其格式化为小数。

(请注意,由于某种原因,Rust的格式化设施不支持D,因此ITIS在此处替换为x。)

错误[E0277]:特质绑定`str:deperhex`不满意 - > <来源>:2:18 | 2 |打印!(" {:x}&#34 ;,"我不是数字"); | ^^^^^^^^^^^^^^^^^^^^^^^ The Trait` Deperhex`未实施`str` =注意:由于“STD:STR` = Noth:Nother` icrum的要求:STD :: FMT :: Departhex :: FMT` =注意:此错误源自宏(夜间构建,使用-z宏回溯运行更多信息)

自2014年左右以来,我在{FMT}库中正在寻求同样的事情,但解决方案仍然融合。自{fmt} 5.0以来的一种方法是基于Michael Park的这个很好的帖子中描述的Constexpr参数仿真技巧:

解决方案来自一个意外的意外(对我)的地方:C ++ 20 Consteval.with C ++ 20现在可以写(Godbolt)

< source>:4:14:错误:呼叫呼叫概念' fmt :: basic_format_string< char,char const(&)[18]> :: basic_format_string< char [5],0>&# 39;不是常量表达式fmt ::打印(" {:d}""我不是数字"); ^ ...... /包括/ fmt / core.h:2587:23:注意:非Constexpr函数' ON_ERROR'不能在常数表达式中使用(规格!=' p' p')eh.on_error("无效的类型说明符"); ^ ... /包括/ fmt / core.h:2808:7:注意:呼叫' check_cstring_type_spec(100,eh)'详情:: check_cstring_type_spec(specs_.type,eh); ^ ... /包括/ fmt / core.h:2456:12:注意:呼叫'& f-> parse(checker(s,{})。上下文_)'返回f.parse(CTX); ^ ... /包括/ fmt / core.h:2718:39:注意:呼叫' parse_format_specs(checker(s,{})。上下文_)'返回ID> = 0&& ID< num_args? parse_funcs_ [id](context_):begin; ^ ... /包括/ fmt / core.h:2382:23:注意:呼叫'& checker(s,{}) - > on_format_specs(0,&" {: d}" [2],&" {:d}" [4])' begin = handler.on_format_specs(适配器.arg_id,begin + 1,结束); ^ ... /包括/ fmt / core.h:2407:21:注意:呼叫' parse_replacement_field(&" {:d}" [1],&&&& 34; {:d}" [4],checker(s,{}))' begin = p = parse_replacement_field(p-1,结束,处理程序); ^ ... /包括/ fmt / core.h:2849:7:注意:呼叫' parse_format_string({&" {:d}" [0],4}, checker(s,{}))'详情:: Parse_Format_String< true>(str_,checker(s,{})); ^<来源>:4:14:注意:呼叫' basic_format_string(" {:d}")' fmt ::打印(" {:d}&#34 ;,#34;我不是数字"); ^ ... /包括/ fmt / core.h:616:29:注意:在此声明fmt_noreturn fmt_api void on_error(const char *消息); ^

编译时调用堆栈类似于您在未捕获异常的情况下获得的运行时堆栈。这部分可能会在将来简化。

{FMT}比RURTON更接近Python,因为Formative格式规格在生锈时,您只有少数的“全局”规格。这对于日期和时间格式特别有用。解析用户定义类型的格式化设计在Constexpr函数中完成,因此在编译时(用于检查或格式字符串编译)和运行时都使用该代码。

在格式字符串的隐式ConstevalConstructor中完成,高级API非常简单:

模板< typename ... t> struct format_string {string_view str;模板< typename s> consteval format_string(const s& fmt):str(fmt){//检查fmt是否是t型t ...}}模板的有效格式字符串< typename ... t> void print(format_string< t ...> fmt,t& ... args);

每当我们调用打印触发Thecheck时,会调用隐式构造函数。实际工作是在格式字符串解析器中完成的,这是一个普通的constexpr C ++代码不值得在此处解释。

C ++ 20 STD :: Format的编译时间检查已被接受到C ++标准(P2216)中,并响起您附近的标准库实现。直到那时,您可以使用{fmt}库,其中包括在upcoming主要版本中的编译时间检查。

我有点担心,编译时间检查可能是不稳定的。但是,在{FMT}的测试套件中启用该功能后,大量格式化函数调用对构建时态的影响非常小,靠近不同构建之间的自然变化。

编译时间检查很酷,但偶尔有用的是具有运行时格式化串。使用getText翻译消息时。它仍然被称为Opt-In(C ++中正确的默认值,震惊!)通过在FMT ::运行时中的格式字符串包装或使用类型删除的vformat(如vformat):

这使得运行时格式字符串在代码中清晰可见,并通过代码查看或工具释放易于封锁。 可以进行键入键的格式字符串进行编译时间检查,该转换数据库中消除了运行时的主要用例,而不是另一个帖子的主题。