关于使用Type Traits检测类成员变量并定义模板函数的技术疑问
首先先放你的参考代码:
template <typename T> auto has_member_k(std::uint32_t) -> decltype(std::declval<T>().k, std::true_type{}); template <typename T> auto has_member_k(...) -> std::false_type; template <typename MyType> typename std::enable_if<decltype(has_member_k<MyType>(0UL))::value, MyType>::type MyFunction(/*some args*/) { /*some code*/ }
你的理解是对的,这段代码核心就是利用**SFINAE(替换失败不是错误)**规则,通过两个重载的has_member_k检测类型T是否拥有成员k,再结合std::enable_if实现模板函数的条件实例化。下面针对你的疑问逐一解释:
疑问1:为什么has_member_k需要std::uint32_t作为输入参数?
这个参数的核心作用是制造重载优先级差异,和成员k的类型没有强制绑定关系,只是作者选了uint32_t而已。
当我们调用has_member_k<MyType>(0UL)时,0UL可以隐式转换为std::uint32_t。如果第一个重载可行(也就是MyType有成员k),编译器会优先匹配这个有具体参数类型的重载——因为在C++重载解析规则里,具体类型的匹配优先级远高于可变参数...。哪怕把这个参数换成int或者bool,只要调用时传的参数能隐式转换过去,效果完全一样。选uint32_t可能只是作者想和成员k的类型呼应,但本质是为了让两个重载有明确的优先级区分,避免歧义。
疑问2:第二个has_member_k是不是仅用于返回false?uint32_t参数会被排除在它的匹配之外吗?
完全正确,第二个重载就是兜底的失败分支:
- 当
MyType没有成员k时,第一个重载的decltype(std::declval<T>().k, ...)会因为表达式非法,被SFINAE规则直接排除出候选重载列表; - 这时候编译器只能匹配第二个可变参数版本,返回
std::false_type,让std::enable_if的条件不成立,从而跳过对应的MyFunction实例化。
而当第一个重载可行时,编译器绝对不会选第二个——因为可变参数...是重载匹配优先级最低的选项,只有没有其他更合适的重载时才会被选中。所以std::uint32_t类型的调用永远不会落到第二个重载上。
疑问3:为什么std::enable_if前面要加typename?
这是因为std::enable_if<...>::type是依赖于模板参数的嵌套类型。在模板定义中,编译器在实例化之前无法确定::type到底是一个类型,还是一个静态成员变量、枚举值之类的东西。
比如这里的std::enable_if<decltype(has_member_k<MyType>(0UL))::value, MyType>::type,整个std::enable_if的实例结果依赖于模板参数MyType,所以它的type成员属于“依赖名称”。必须用typename关键字明确告诉编译器:“后面的::type是一个类型,不是其他实体”。如果不加typename,编译器会直接报错,因为它无法正确解析这个嵌套名称。
内容的提问来源于stack exchange,提问作者roschach

