std::is_same处理结构类型非类型模板参数的判定规则问题
场景说明
考虑包含任意类型结构型非类型模板参数的模板类型,本示例中定义了value_type<auto V>。
我们同时声明一个constexpr结构体,其包含若干整型成员,自定义构造函数通过非平凡表达式(逻辑复杂度高于直接内存拷贝)设置成员值。
尝试将该结构体实例传入value_type作为非类型模板参数,并使用std::is_same比较不同实例对应的模板类型是否相等。
用于演示该场景的示例代码如下:
#include <iostream> #include <type_traits> template <auto V> struct value_type { using type = decltype(V); static constexpr type value = V; }; struct hmm { // 两个构造函数默认都会将b设为4 constexpr hmm(int x, int y = 8) : a(x), b(y / 2) { } constexpr hmm(float c, int z = 2) : a((int)c), b(z * 2) { } const int a; const int b; friend constexpr bool operator==(const hmm& a, const hmm& b) { return false; } friend constexpr bool operator!=(const hmm& a, const hmm& b) { return true; } }; int main() { std::cout << (std::is_same_v<value_type<hmm(2)>, value_type<hmm(3.5f)>>) << ", "; std::cout << (std::is_same_v<value_type<hmm(5)>, value_type<hmm(5.11112f)>>) << ", "; std::cout << (std::is_same_v<value_type<hmm(5, 7)>, value_type<hmm(5.11112f)>>) << ", "; std::cout << (std::is_same_v<value_type<hmm(5, 12)>, value_type<hmm(5.11112f, 3)>>) << std::endl; return 0; }
上述代码在GCC、MSVC、Clang三大编译器下均输出0, 1, 0, 1,结果符合直觉,但该机制的判定边界仍不明确,以下针对提出的技术问题逐一解答:
问题解答
1. 不同结构体实例生成的模板类型,相等比较的具体执行逻辑是什么?
这类比较本质是非类型模板实参的同一性判定,和用户自定义的operator==完全无关:编译器会在常量求值上下文完成非类型模板实参的构造后,对两个实参做递归逐成员值校验:
- 对基础类型成员,直接比较存储的常量值是否相等
- 对类类型、数组成员,递归执行逐成员比较
- 所有成员值完全相等的两个常量实参,就会被判定为等价,对应的模板实例就是同一个类型
示例中自定义的operator==永远返回false,但完全不影响判定过程,因为模板实参等价性判定从根本上不会调用用户自定义的比较运算符。
2. 该行为是C++标准明确规定的跨编译器统一行为,还是编译器实现巧合?
这是C20标准正式明确规定的跨编译器统一行为,不是实现巧合。
C20放宽了非类型模板参数的限制,允许字面量类类型作为非类型模板参数,同时在核心语言规则中明确规定了这类实参的等价性判定标准:两个同类型的非类型模板实参等价,当且仅当它们的所有子对象(包括基类子对象、成员子对象、数组成员)的值都对应相等,不存在任何成员值差异。三大编译器输出一致,是严格遵循标准强制要求的结果。
3. 是否存在符合C++标准的方式,自定义该场景下的类型相等比较逻辑?
不存在符合标准的自定义方式。
非类型模板实参的等价性判定是模板实例化过程中的核心语义规则,属于编译器实现层面的硬逻辑,标准没有给用户留出自定义挂钩的入口。用户重载的operator==只会在普通代码主动调用时生效,完全不会介入模板实参等价性判定流程。
如果需要实现自定义等价规则,只能通过额外封装模板元逻辑实现,比如自行编写类型特征类针对特定结构体类型做自定义比较,无法修改std::is_same本身对这类模板实例的判定结果。
std::is_same基于<T,T>偏特化的典型实现如下:
template<class T, class U> struct is_same : std::false_type {}; template<class T> struct is_same<T, T> : std::true_type {};
4. 在<T,T>模板特化匹配场景下,判定两个类型T、U为同一实体的具体规则是什么?
模板偏特化匹配时的类型同一性判定,核心规则是两个类型如果是同一个模板的实例,且所有模板实参都满足等价要求,就判定为同一类型:
- 如果两个类型是不同模板生成的实例,必然不是同一类型
- 如果是同一个模板生成的实例,所有类型模板实参为同一类型、所有非类型模板实参满足等价要求、所有模板模板实参为同一模板,就会判定为同一类型,匹配
<T,T>偏特化。std::is_same本身没有额外的比较逻辑,完全依赖编译器内置的这套类型同一性判定规则返回结果。
内容的提问来源于stack exchange,提问作者noisy cat

