本周在圣路易斯举行的 WG21 会议议程中有一篇论文P3116 “Policy for explicit”(Zach Laine,2024 年)。“政策”的概念是 LEWG 希望为提案作者制定一个类似“风格指南”的东西。如果提案中noexcept位置错误、explicit或[[nodiscard]]使用不当,能快速告知作者应如何修改,避免在每篇论文上都进行相同讨论。
P3116 的基本想法是列出 C++标准文档的当前“内部风格”(特别是与explicit关键字相关的),不是创新或开拓新领域,而是描述库条款目前的做法,以保持与这种内部风格的一致性。
作者自己对于行业代码库中explicit使用的指南是:默认情况下所有构造函数都应为explicit,非explicit构造函数用于特殊情况。
但标准库规范有 40 年历史,不能像新行业代码那样,如vector的迭代器对构造函数为非explicit会导致未定义行为,但已有大量代码依赖此类构造函数,不能轻易更改。
作者理解的 STL 的explicit内部风格(不适用于行业代码)如下:
- 规则 0:偏离以下规则需有充分理由,“我喜欢”或“我的雇主这样做”不是好理由。
- 规则 1:一般除
operator bool外不应有operator X转换函数,有正当理由的偏差包括basic_string::operator basic_string_view和explicit chrono::year::operator int。 - 规则 2:
operator bool应始终为explicit,如unique_ptr::operator bool和optional::operator bool,但bitset::reference::operator bool为非explicit。 - 规则 3:构造函数的适当
explicit性主要取决于参数数量,单参数构造函数应为explicit,多参数构造函数应为非explicit,若单个重载参数可变需拆分重载。 - 规则 4:初始化列表构造函数、复制构造函数和移动构造函数应为非
explicit,其他单参数构造函数应为explicit,但有正当理由的偏差,如string(const char*)。 - 规则 5:若类型提供非
explicit初始化列表构造函数,也应提供非explicit默认构造函数,因为{}初始化使用默认构造函数。 - 规则 6:除特定情况外,零参数构造函数应为非
explicit。 - 规则 7:若类型为“标记类型”,其零参数构造函数应为
explicit,因为非explicit构造函数可能无效,如in_place_index_t<N>。 - 规则 8:参数数量为 2 或更大的构造函数应为非
explicit,如pair(piecewise_construct_t, tuple, tuple),但有正当理由的偏差,如explicit optional(in_place_t, Args&&...)。 - 规则 9:推导指南不应为
explicit。
当前标准库中的偏差:
- 在
<chrono>中有多个转换函数为explicit,其他地方也有一些转换函数为explicit的偏差。 - 一些类型的构造函数存在偏差,如
vector<bool>::reference::operator bool等。 - 许多类型的单参数转换构造函数存在偏差。
string_view和span的转换构造函数存在偏差。scoped_allocator_adaptor的构造函数存在偏差。- 许多异常类型的构造函数存在偏差。
- 一些标准库中的容器和实用类型的构造函数存在偏差,如各种流类、
unordered_*系列等。 - 一些代数类型的构造函数存在偏差。
locale::facet及其子类的构造函数存在偏差。string的构造函数存在偏差。span的构造函数存在偏差。mdspan库的构造函数存在较多偏差。out_ptr_t的构造函数存在偏差。- [thread]中的一些类型的构造函数存在偏差。
- [rand]中的一些分布类的构造函数存在偏差。
- 所有 Ranges 视图类型的构造函数存在偏差。
标准中的“标记类型”遵循规则 7 放弃规则 6 且不偏离规则。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。