C++标准委员会7月科隆会议中投票通过的特性


声明:本文转载自https://my.oschina.net/u/4173234/blog/3080199,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

引言

上周 C++ 标准委员会在科隆举行了7月会议(WG21)。 会议报告请戳:《Trip Report: C++ Standards Meeting in Cologne, July 2019》

会议中重点针对 C++20 标准已经投票通过的特性进行了若干修正,并对一些草案进行了讨论和投票。相比上次会议,通过了一些新的草案。

移除的语言特性

新增的主要语言特性

新增的主要标准库特性

新增特性介绍

上述特性是本人认为的本次会议投票通过的主要特性。若要浏览全部特性列表,请戳引言中的连接。

下面我将其中一些关键特性作简要介绍。

constinit 关键字

这是新引入的一个关键字,用于强制标记一个全局变量的初始化在编译期完成。若初始化表达式无法在编译期求值,则会引发编译错误。这样便可以避免全局变量的隐式运行时初始化带来的各种各样难以调试的 BUG。

示例:

const char* g() { return "运行时初始化"; }
constexpr const char* f(bool p) { return p ? "常量初始化" : g(); }

constinit auto c = f(true); // OK
constinit auto d = f(false); // 编译错误

using enum

这个特性算是对现有 using 语句用法的一个功能补全。它允许 using 语句运用在 enumenum class 之中。

给出枚举:

// china_railway.hpp
enum class china_railway
{
	cr400af,
	cr400bf,
	cr200j
};

用法1:

using enum china_railway;
constexpr auto train_type = cr400af;

用法2:

struct foo
{
	// 引入枚举成员。
	using enum china_railway;
};

void geek()
{
	foo f;
	auto a = f.cr400af; // 使用类实例,合法
	auto b = foo::cr200j; // 使用类名,合法
}

用法3:

using china_fuxing_trains = china_railway;
using rubbish = china_railway::cr200j; // 枚举值别名
constexpr auto type = china_fuxing_trains::cr400bf;
constexpr auto fake_emu_train = rubbish; // 和 cr200j 等价

文本格式化支持

这是继模块、协程和概念后又一个重磅特性。它弥补了 C++ 标准库缺乏文本格式化支持的一个遗憾。这次通过的提案基于开源库 fmt,语法十分优雅。文本格式化主要通过两个新的标准库函数 std::formatstd::format_to 来实现。

示例1:std::format 基本用法

// 自动编号
auto text1 = std::format("{} 是动车组,但 {} 却不是。", "CR400AF", "CR200J");

// 手动编号
auto text2 = std::format("我国领土面积 {0} 万平方公里,人口有 {1} 亿。", 960, 14);

// 取消“{}”的转义。
// text3 = "我们的分组是:{100, 200, 300}。"
auto text3 = std::format("我们的分组是:{{{0}, {1}, {2}}}。", 100, 200, 300);

示例2:std::format 格式化控制

std::format 函数支持高级格式化控制。它使用格式控制表达式进行格式化输出。标准格式控制表达式的形式如下所示。

"{[arg_index]:[[fill]align][sign]['#']['0'][width]['.' precision][type]}"

看似很复杂的亚子,其实不难,让我们通过如下的表格来解析。

说明:上式中的方括号[]代表为可选参数。

参数取值说明
arg_id参数编号如 0,1,2,...
fill除了 '{' 和 '}' 外的任意字符用于格式填充的字符
align'<', '>', '=', '^'控制对齐方式
sign'+', '-', '空格'控制正负号显示方式
'#'控制数值类型输出是否添加前缀
width非零整数 或 '{' arg_id '}'控制文本长度,若不足将使用 fill 填充
precision整数 或 '{' arg_id '}'控制小数点后的保留的位数
type'a', 'A', 'b', 'B', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'p', 's', 'x', 'X'控制呈现格式

怎么样,是不是通过上表的说明,大家对这个格式控制有了一个初步的理解呢?下面我们继续展开,深入讲讲 alignsigntype参数取值的具体意义。

表格1: align 参数说明

align说明
'<'强制左对齐。若长度不足 width,则从字符串末尾进行填充。
'>'强制右对齐。若长度不足 width,则从字符串开始进行填充。
'='仅用于数值类型。若长度不足 width,则从 sign 或前缀之后、数字开始之前进行填充。
'^'强制居中对齐。若长度不足 width,则在字符串两侧同时进行填充。

表格2: sign 参数说明

sign说明
'+'针对正数显式添加 '+' 号,如 "+123"。
'-'针对负数显式添加 '-' 号,如 "-123";针对正数不添加符号,如 "123"。这是默认选项。
'空格'针对正数在头部添加一个空格,如 " 123";针对负数添加 '-' 号,如 "-123"。

表格3: type 参数说明

type说明'#' 作用
's'按原始字符串输出,仅用于字符串。
'c'按原始字符输出,仅用于 char8_t, char16_t, char32_t, char, wchar_t 类型。
'b', 'B'按二进制输出,仅用于整数类型,如 "1010"。'b' 添加 "0b"; 'B' 添加 "0B"
'd'按十进制输出,仅用于整数类型,如 "12345"。
'o'按八进制输出,仅用于整数类型,如 "10"。添加 "0"。
'x', 'X'按十六进制输出,仅用于整数类型。'x' 输出小写,'X' 输出大写。'x' 添加 "0x";'X' 添加 "0X"
'n'意义同 'd',但在其基础上,根据本地语言设置,添加十进制的分隔符,如:"1,000,000"。
'a', 'A'按十六进制输出,并根据 precision 保留小数,仅用于浮点类型。'a' 输出小写,'A' 输出大写。
'e', 'E'按科学计数法输出,仅用于浮点类型。若 precision 省略,则默认为 6。'e' 输出小写,'E' 输出大写。
'f', 'F'按十进制输出,并根据 precision 保留小数,仅用于浮点类型。若 precision 省略则默认为 6。'f' 输出小写,'F' 输出大写。
'g', 'G'按十进制 + 科学计数法方式输出。若 precision 省略则默认为 6。'g' 输出小写,'G' 输出大写。
'n'意义同 'g',但在其基础上,根据本地语言设置,添加十进制的分隔符,如 "1,000.456"。
'p'按十六进制输出指针值,仅用于指针类型,如 "FFFFFFFF"。添加 "0x"

写到这,不得不感叹,这次添加进标准的格式化函数还是非常完备的,考虑到了各种类型的格式化。理论用于实际,让我们看看一些实例吧。

// 高级用法:格式化控制符
char c = 120;
auto s0 = std::format("{:6}", 42);      // s0 == "    42"
auto s1 = std::format("{:6}", 'x');     // s1 == "x     "
auto s2 = std::format("{:*<6}", 'x');   // s2 == "x*****"
auto s3 = std::format("{:*>6}", 'x');   // s3 == "*****x"
auto s4 = std::format("{:*^6}", 'x');   // s4 == "**x***"
auto s6 = std::format("{:6d}", c);      // s6 == "   120"
auto s7 = std::format("{:=+06d}", c);   // s7 == "+00120"
auto s8 = std::format("{:0=#6x}", 0xa); // s8 == "0x000a"
auto s9 = std::format("{:6}", true);    // s9 == "true  "

示例3:std::format_to 的用法

std::format_to 函数主要用于容器类型,如 std::vector<T> 的格式化添加值。

#include <vector>
#include <format>
#include <string>

int main()
{
	std::vector<std::string> data;
	for (size_t i = 0; i < 100; i++)
	{
		std::format_to(std::back_inserter(data), "我的工号是:{} ", i);
	}
}

其中格式字符串的用法等同于 std::format 函数,可参考上述有关内容。

std::source_location

这是一个比较实用的特性。它为标准库新增了一个元数据类 std::source_location,可为用户提供当前代码的上下文信息。该类的定义如下所示。

namespace std
{
	struct source_location
	{
		constexpr source_location() noexcept;
		constexpr uint_least32_t line() const noexcept;
		constexpr uint_least32_t column() const noexcept;
		constexpr const char* file_name() const noexcept;
		constexpr const char* function_name() const noexcept;
		static consteval source_location current() noexcept;
	};
}

示例:

#include <source_location>
#include <format>
#include <iostream>

void foo()
{
	// Get the location of this line.
	auto loc = std::source_location::current();
	
	std::cout << std::format("Line: {}, Column: {}, Function: {}, File: {}", loc.line(), loc.column(), loc.function_name(), loc.file_name()) << std::endl;
}

概念(Concepts)采用标准库命名规范

这个特性是指原本采用 Pascal 命名法的概念,在这次会议中投票通过改为与标准库一致的小写下划线命名法。如:

std::Invocable 改为 std::invocable

std::ConvertibleTo 改为 std::convertible_to

std::EqualityComparable 改为 std::equality_comparable

等等

总结

由于这次投票通过的特性还是比较多的,我只挑选了几个有代表性的展开,其他的就不再一一赘述。(毕竟肉翻还是很费脑细胞的233)。感兴趣的同学可以去看我在引言中的原文链接查阅。由于本人水平有限,若有瑕疵,在所难免。欢迎大家拍砖指正!

本文发表于2019年07月29日 11:00
(c)注:本文转载自https://my.oschina.net/u/4173234/blog/3080199,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1774 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1