本文主要探讨了 C++ 中类类型作为非类型模板参数的相关问题,包括默认的<=>设计、各种设计方案的优缺点以及最终提出的解决方案等,具体内容如下:
- Setting the Stage:之前讨论了
constexpr分配的问题,今天继续探讨一系列难以解决的问题背后的原因,如 C++20 之前非类型模板参数只能是标量类型、指针和引用等,而不能是类类型。处理类类型的难点在于如何确定模板参数等价性,避免二次比较和 One Definition Rule(ODR)违规等问题。后来采用了<=>操作符,但其基于默认<=>的模板参数等价定义方法存在一些不一致性,如运行时和编译时结果不同、顶层模板参数和类成员行为不同等,最终在 C++20 中基于 Jeff 的原始想法制定了新的设计,即直接比较类对象的所有成员来确定模板参数等价性,但仍存在一些问题,如对于std::string和std::vector等类型不适用。 - A Serialization Problem:模板参数等价性是一个序列化问题,将类类型视为由一堆标量类型组成,需要用户告知编译器类的分解方式,即如何序列化类。序列化将值分解为结构类型的元组,编译器可递归处理,对于不同类型如
std::tuple、std::optional、std::variant、std::string和std::vector等,序列化方式不同,需要考虑成员的公开性等因素。 - A Normalization Problem:以
SmallString类为例,讨论了序列化过程中的规范化问题,如SmallString{1, 2}是否应与SmallString{2, 4}模板参数等价,以及是否应允许序列化过程进行规范化以减少模板实例化等。规范化可以避免 ODR 违规,但需要类作者识别并避免陷阱,否则可能导致不可预测的行为。 - A Deserialization Problem:提出了另一种解决序列化问题的方法,即通过反序列化步骤将序列化后的表示转换为实际的模板参数,这样可以避免 ODR 违规,并且编译器可以进行额外的序列化检查以确保反序列化的正确性。反序列化过程可以递归进行,对于一些类型如
std::vector,仅比较指针值是不够的,需要比较内容。 - What are the Options?:Richard Smith 提出了五种支持类类型作为非类型模板参数的潜在解决方案,包括成员级序列化、自定义序列化、自定义序列化与手动规范化、自定义序列化与反序列化、自定义手动规范化与自动序列化等,其中自定义序列化与反序列化是最通用的解决方案,但仍需考虑如何设计 API 以减轻程序员的负担。
- The Paper:Richard Smith 和作者于 2021 年底撰写了一篇论文[P2484R0],基于自定义序列化和反序列化设计来扩展类类型作为非类型模板参数的支持,但该设计存在一些问题,如不支持私有成员的成员级模板参数等价、无法清晰处理变长数据、
operator template的可调用性受限等,但该方法仍有一些优点,如可以正确处理引用成员、防止指针成员的错误操作等。 - Reflection Will Fix It:随着 Reflection([P2996])在 C++26 中的进展,利用其提供的功能可以更好地解决序列化问题,如对于
SmallString、std::vector、std::optional等类型,可以通过序列化到std::meta::info范围来实现序列化和反序列化,避免了对std::vector等类型的特殊处理,但对于tuple类型的实现仍然比较复杂,提出允许operator template返回void,可以简化optional、tuple等类型的实现,并使规范化案例更容易实现。 - A Proposal Emerges:提出了一个扩展 C++20 规则的提案,包括扩展结构类型的定义、引入规范化步骤和改变模板参数等价性的比较方式等,为不同情况提供了最小化的选择,对于简单情况提供了单空
void函数的默认选择,对于需要规范化的情况提供了单void函数进行突变的选择,对于困难情况提供了返回meta::info范围的选择,该提案看起来很有吸引力,但仍需考虑是否存在未解决的问题。 - Ending with a Fun Example:以
Fun类为例,展示了在处理类类型作为非类型模板参数时可能遇到的问题和不同的解决方法,如通过自定义序列化或修改规则来使类类型工作,但这些方法都有各自的优缺点,需要根据具体情况进行选择。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。