禁用某些构造函数
有时候我们希望用更明确的自定义类型取代一些 primitives,依靠类型系统来减少一些人为错误:
1 | void Run(WithMultithreading mt, WithAdvancedMode advanced); |
类型 WithMultithraeding
和 WithAdvancedMode
用起来很像 bool
,但是他们是两个不同的类型,混用会出现编译错误。
1 | Run(mode, mt); // compile failure; type mis-match |
不管我们使用 typedef
还是 using
,都只能定义 bool
的类型别名;在类型系统看来他们还是一回事。
一个比较常用的方案是使用 class template + type tag,即
1 | template<typename T> |
注:tag type 不需要提前定义,直接在需要定义 type alias 提供一个即可。
然而上面的构造函数定义无法保证 TaggedBool
只能从 bool
或者其他 TaggedBool
初始化。
因为如下的代码仍然能编译通过,虽然在某些严格编译模式下会有告警。
1 | int i = 123; |
问题的核心是某个类型可以隐式转换到 bool
,进而再调用 TaggedBool
的构造函数。
explicit
和 {}
初始化在这里都没有什么帮助。
Rant:如果问我最不喜欢哪个 C++ 特性,implicit conversion 绝对名列前茅。
如果我们能提供接受这些参数的构造函数版本,并将它们标记为 delete
,那么编译器便会选中这些被”删除“的函数,触发编译错误;以做到禁止这些构造函数的目的。
同时因为我们需要禁用一系列的构造函数,我们最好通过函数模板来减少工作量。
我们需要对函数模板进行类型限制,目前最常见的做法就是利用 SFINAE,未来 C++ 20 的 concepts 普及后可以直接利用 concepts。
1 | template<typename T> |
这样一来,如果我们使用其他integral类型,浮点类型,甚至指针类型都会强迫编译器选择这个被 delete 的函数版本,强制触发编译失败。
完整代码可以见这里