如何不依赖SFINAE与辅助结构体实现TagOrInt类型别名?
如何不依赖SFINAE/辅助结构体实现条件类型别名
TagOrInt<T> 需求说明
我想要定义类型别名TagOrInt<T>,规则如下:
- 当类型
T存在成员类型Tag时,TagOrInt<T>等价于T::Tag; - 当
T不存在Tag成员类型时,TagOrInt<T>等价于int。
期望效果可用伪代码表示:
template <class T> using TagOrInt = typename T::Tag; // 仅当该声明合法时生效 template <class T> using TagOrInt = int; // 否则生效
现有SFINAE实现方案
目前我可以借助SFINAE特性和辅助结构体实现需求,代码如下:
#include <type_traits> template<class T, class = void> struct TagOrIntHelper { using type = int; }; template<class T> struct TagOrIntHelper<T, std::void_t<typename T::Tag>> { using type = typename T::Tag; }; template<class T> using TagOrInt = TagOrIntHelper<T>::type; // 测试用例 struct X { using Tag = float; }; struct Y { }; static_assert(std::is_same_v<TagOrInt<X>, float>); static_assert(std::is_same_v<TagOrInt<Y>, int>);
该方案可以正常工作,但我希望找到更简洁的实现方式,比如不依赖SFINAE和辅助结构体,或者利用C20 Concepts特性,以及C17中的优化写法。
尝试C++20 Concepts失败的问题
我尝试用C++20 Concepts实现,但编译失败了,代码如下:
#include <type_traits> template<class T> concept HasTag = requires { (typename T::Tag *)nullptr; }; template<class T> using TagOrInt = std::conditional_t<HasTag<T>, typename T::Tag, int>; // 测试用例 struct Y { }; static_assert(std::is_same_v<TagOrInt<Y>, int>);
编译错误信息:
concept.cc:5:7: error: no type named ‘Tag’ in ‘struct Y’
原因是std::conditional_t会强制实例化两个分支的类型表达式,即便其中一个分支不会被最终选中。当处理TagOrInt<Y>时,typename T::Tag会被尝试解析,而Y没有Tag成员,因此触发编译错误。
可行解决方案
C++20 Concepts 正确实现
利用Concepts的模板约束特性,通过重载类型别名来避免无效分支的实例化:
#include <type_traits> template<class T> concept HasTag = requires { typename T::Tag; }; // 优先匹配满足HasTag约束的模板参数 template<HasTag T> using TagOrInt = typename T::Tag; // 匹配不满足HasTag约束的模板参数 template<class T> requires (!HasTag<T>) using TagOrInt = int; // 测试用例 struct X { using Tag = float; }; struct Y { }; static_assert(std::is_same_v<TagOrInt<X>, float>); static_assert(std::is_same_v<TagOrInt<Y>, int>);
这种方式下,编译器只会根据模板参数是否满足HasTag约束,选择对应的类型别名定义,不会触发无效分支的类型解析。
C++17 简化实现
C++17中没有Concepts,但基于std::void_t的SFINAE方案已是最简洁的实现方式,无需额外冗余代码:
#include <type_traits> template<class T, class = void> struct TagOrIntHelper { using type = int; }; template<class T> struct TagOrIntHelper<T, std::void_t<typename T::Tag>> { using type = typename T::Tag; }; template<class T> using TagOrInt = TagOrIntHelper<T>::type; // 测试用例 struct X { using Tag = float; }; struct Y { }; static_assert(std::is_same_v<TagOrInt<X>, float>); static_assert(std::is_same_v<TagOrInt<Y>, int>);
std::void_t是C++17引入的特性,它可以简洁地触发SFINAE逻辑,无需手动编写复杂的检测逻辑。
内容的提问来源于stack exchange,提问作者Sven Sandberg
相关产品推荐
相关产品推荐

