如何实现跨编译器兼容的多std::void_t类型特性偏特化?
解决
holder_type_of特化冲突与跨编译器兼容问题 问题根源
你编写的前两个类模板偏特化在Clang中被判定为重定义,核心原因是模板签名等价性:
std::void_t<typename T::red>和std::void_t<typename T::blue>的实例化结果都是void- 两个偏特化的模板参数列表均为
template <class T, class Void>,第二个模板参数的最终类型都是void,第三个参数都是未约束的模板参数Void - 根据C++标准,这种情况下两个偏特化的签名等价,属于非法重定义。GCC的宽松处理反而不符合标准要求。
修复方案
方案1:基于独立检测特性的分支判断
先定义两个独立的特性检测模板,再通过逻辑分支推导最终类型:
#include <type_traits> enum class holder_type { none, red, blue, both }; // 检测T是否包含red别名 template <class T, class = void> struct has_red : std::false_type {}; template <class T> struct has_red<T, std::void_t<typename T::red>> : std::true_type {}; // 检测T是否包含blue别名 template <class T, class = void> struct has_blue : std::false_type {}; template <class T> struct has_blue<T, std::void_t<typename T::blue>> : std::true_type {}; // 基于检测结果推导holder_type template <class T> struct holder_type_of : std::integral_constant<holder_type, has_red<T>::value ? (has_blue<T>::value ? holder_type::both : holder_type::red) : (has_blue<T>::value ? holder_type::blue : holder_type::none) > {}; // 测试用例示例 struct red_holder { using red = int; }; struct blue_holder { using blue = int; }; struct both_holder { using red = int; using blue = int; }; struct none_holder {}; static_assert(holder_type_of<red_holder>::value == holder_type::red); static_assert(holder_type_of<blue_holder>::value == holder_type::blue); static_assert(holder_type_of<both_holder>::value == holder_type::both); static_assert(holder_type_of<none_holder>::value == holder_type::none);
方案2:使用非类型模板参数区分特化
通过bool类型的非类型参数,明确区分不同的特性组合,避免签名冲突:
#include <type_traits> enum class holder_type { none, red, blue, both }; template <class T, class = void> struct has_red : std::false_type {}; template <class T> struct has_red<T, std::void_t<typename T::red>> : std::true_type {}; template <class T, class = void> struct has_blue : std::false_type {}; template <class T> struct has_blue<T, std::void_t<typename T::blue>> : std::true_type {}; // 主模板:默认无任何别名 template <class T, bool HasRed = has_red<T>::value, bool HasBlue = has_blue<T>::value> struct holder_type_of : std::integral_constant<holder_type, holder_type::none> {}; // 特化:仅包含red别名 template <class T> struct holder_type_of<T, true, false> : std::integral_constant<holder_type, holder_type::red> {}; // 特化:仅包含blue别名 template <class T> struct holder_type_of<T, false, true> : std::integral_constant<holder_type, holder_type::blue> {}; // 特化:同时包含两个别名 template <class T> struct holder_type_of<T, true, true> : std::integral_constant<holder_type, holder_type::both> {}; // 测试用例同方案1
关于Clang的行为是否符合标准
Clang的行为完全符合C++标准。根据C++标准[temp.class.spec]章节规定:两个类模板偏特化若要被视为不同特化,必须满足模板参数列表不等价,或者模板实参列表不等价。你编写的前两个偏特化,第二个模板参数的实例化结果都是void,第三个参数都是未约束的模板参数Void,因此签名等价,属于非法重定义。GCC的处理属于非标准的宽松实现。
内容的提问来源于stack exchange,提问作者Jan Schultke
相关产品推荐
相关产品推荐

