std::has_unique_object_representations
这个东西用于判断是否有一个唯一性的字节表示。啥意思呢?比如我们某个结构体,可能因为不同的内存对齐要求而插入padding。但是这个8字节对齐的结构体和4字节对齐的结构体的实际含义相同,但是底层的二进制表示却不同。这时候我们就说这个对象的字节表示不唯一。说白了,只要是模糊地带,比如:虚函数,填充位,布尔值,架构,编译器相关的这种东西都会导致它返回false。那么它能干什么?他能干的事情主要是帮助我们进行序列化或让我们判断是否可以直接memcpy
std::monostate
这个之前居然一直忘了写。
1
struct monostate { };
他的定义就这么简单,啥都没有。为啥有这个?
有意作为 std::variant 中的行为良好的空可选项的单位类型。具体而言,非可默认构造的变体类型可以把
std::monostate
列为其首个可选项:这使得此变体自身可默认构造。
对的 就是如果variant
里面的类型,第一个为非可默认构造的,那么就用monostate
放在前面占位。
1
2
3
4
5
6
7
8
9
10
11
struct s{};
struct t{
t(int) {}
};
int main(){
std::variant<s, t> v{};
std::variant<t, s> v2 {}; // 不行,t不可默认构造
std::variant<std::monostate, t, s> v1 {}; // OK
}
有人问为啥不直接给s放到t前面。主要是有些场合,t在s构造或有值前是无意义的。他不应该为variant的默认型别。
除此之外还能干什么?
首先注意一下,他并不是空类型。但从严格意义上来讲,它同 void
一样,是一个单值类型。C++ 并不像 Rust、Haskell、Scala 等语言那样存在真正的“空”类型。真正的“空”类型无法表示任何值,也无法被创建,所以一个以“空”类型作为参数的函数永远也无法被调用。
std::monostate
类型可以被创建,但该类型只能够包含一个值,因此它也是一个 Unit Type。单值类型只能表示一个状态,这种特殊性使其成为一个没有意义的类型。std::nullptr_t
也是这样的一种类型,不过因为它能够隐式转换成任意的指针类型,并且可以被流输出,误用的几率较大,所以不适合用来作为通用的无意义表示类型。
std::monostate
支持默认构造、拷贝构造和所有的比较操作,是一个常规类型,而 void
是由关键字声明的内置类型,它是一个不完整类型(Incomplete Type),无法创建对象。因此,虽然都是 Unit Type,但 std::monostate
存在一些特殊的使用场景:
第一,你可以用它来测试模板容器的健壮性。因为 std::monostate
不支持输出、运算、隐式转换等功能,也不包含任何成员,是最简单的常规类型,所以可以尝试以其作为模板参数实现化容器,如果没有报错,就表示容器的设计合理,没有强依赖。
第二,你可以将它作为可选模板参数的默认类型。
1
2
3
4
5
template<typename ExtraInfo = std::monostate>
class Data {
// ...
ExtraInfo info;
};
用户可以选择是否传递额外的信息,std::monostate
作为默认类型,不会误用、不占空间、也不会和其他类型产生冲突。
第三,你可以将它作为无意义成员的替代类型。
1
2
3
4
5
6
7
8
9
10
11
12
template<bool Debug>
class S {
public:
void log_info(const std::string& msg) {
if constexpr (Debug) {
log_.info(msg);
}
}
private:
std::conditional_t<Debug, Log, std::monostate> log_;
};
这样,在非调试状态下,代码依旧可以正常编译,却不会具备真实的功能。
第四,作为线性递归的 Root 类型。在 C++ Generative Metaprogramming 一书的第 6.3.1 小节,单独写了一个 struct empty_type{}
作为线性递归继承的默认结束条件,那是为了通用性考虑。如果在 C++17 之后也有类似的需求,那么便可以直接使用已经存在的 std::monostate
类型。