寻找无需评估两种类型的std::conditional_t替代方案
解决std::conditional_t必须同时评估两种类型的替代方案
问题背景
std::conditional_t的特性是必须同时评估两个分支的类型,这在很多场景下会造成编译错误——比如当其中一个分支的类型并不存在时。我需要实现这样的逻辑:处理类(例如视图类)根据被处理类(例如被视对象)是否具备特定嵌套类型Type,来决定自身的内部类型。典型场景比如适配功能不全的矩阵类,这类矩阵可能只支持部分访问方式,视图类需要动态匹配其可用的类型。
现有四种解决方案
我已经实现了四种可行方案,代码如下:
#include <iostream> #include <vector> template<typename T> concept IsAccepted = requires() { typename T::Type; }; struct A { typedef std::vector<double> Type; }; struct B { typedef bool Type; }; struct C {}; typedef int D; // 方案1:直接使用std::conditional_t,存在致命问题 template<typename T> struct View1 { // 问题:无论条件是否满足,T::Type都会被评估,导致C/D这类无Type的类型编译失败 typedef std::conditional_t<IsAccepted<T>, typename T::Type, void> Type; }; // 方案2:用哑元类替代无效分支 template<typename T> struct View2 { struct Dumb { typedef void Type; }; // 思路:条件满足时取T::Type,否则取Dumb::Type,避免直接评估不存在的类型 typedef typename std::conditional_t<IsAccepted<T>, T, Dumb>::Type Type; }; // 方案3:通过带约束的静态函数推导类型 template<typename T> struct View3 { // 约束匹配时返回T::Type,否则返回void template<typename S> requires( IsAccepted<S>) static constexpr typename S::Type get(); template<typename S> requires(!IsAccepted<S>) static constexpr void get(); // 通过decltype推导get<T>()的返回类型作为内部Type typedef decltype(get<T>()) Type; }; // 方案4:利用if constexpr的分支特性推导类型 template<typename T> struct View4 { static constexpr auto get() { // if constexpr只会评估满足条件的分支,避免无效类型的评估 if constexpr (IsAccepted<T>) return (typename T::Type*) {}; else return (void*) {}; } // 移除指针后得到目标类型 typedef std::remove_pointer_t<decltype(get())> Type; }; int main() { View1<A> a1; View1<A>::Type a1t{}; View1<B> b1; View1<B>::Type b1t{}; //View1<C> c1; // 编译失败!C::Type不存在但被强制评估 //View1<D> d1; // 编译失败!int::Type不存在但被强制评估 View2<A> a2; View2<A>::Type a2t{}; View2<B> b2; View2<B>::Type b2t{}; View2<C> c2; //View2<C>::Type c2t{}; // 类型为void,无法实例化 View2<D> d2; //View2<D>::Type d2t{}; // 类型为void,无法实例化 View3<A> a3; View3<A>::Type a3t{}; View3<B> b3; View3<B>::Type b3t{}; View3<C> c3; //View3<C>::Type c3t{}; // 类型为void,无法实例化 View3<D> d3; //View3<D>::Type d3t{}; // 类型为void,无法实例化 View4<A> a4; View4<A>::Type a4t{}; View4<B> b4; View4<B>::Type b4t{}; View4<C> c4; //View4<C>::Type c4t{}; // 类型为void,无法实例化 View4<D> d4; //View4<D>::Type d4t{}; // 类型为void,无法实例化 }
更具可读性的实现方式
以上方案中,方案2、3、4都能解决编译问题,但可读性各有优劣。这里提供两种更简洁直观的实现:
方式1:模板特化(传统且清晰)
利用模板特化直接匹配满足条件的类型,逻辑一目了然:
#include <vector> #include <type_traits> template<typename T, typename = void> struct View { using Type = void; // 默认类型为void }; template<typename T> struct View<T, std::void_t<typename T::Type>> { using Type = typename T::Type; // 当T存在Type时,使用该类型 }; // 测试用类型 struct A { using Type = std::vector<double>; }; struct B { using Type = bool; }; struct C {}; using D = int;
这种方式依赖std::void_t(C++17引入),通过SFINAE机制筛选出具备T::Type的类型,代码结构清晰,维护成本低。
方式2:C++20 Concept + 别名模板
结合Concept的约束能力,直接用别名模板实现,代码更紧凑:
#include <vector> template<typename T> concept HasType = requires { typename T::Type; }; // 分情况定义别名模板,完全避免双分支评估问题 template<typename T> requires HasType<T> using ViewType = typename T::Type; template<typename T> requires (!HasType<T>) using ViewType = void; // 测试用类型 struct A { using Type = std::vector<double>; }; struct B { using Type = bool; }; struct C {}; using D = int;
这种写法通过分情况定义别名模板,完全规避了std::conditional_t的双分支评估问题,逻辑直白,可读性拉满。
内容的提问来源于stack exchange,提问作者Chameleon
相关产品推荐
相关产品推荐

