【C++基础知识】C++类型特征组合:`disjunction_v` 和 `conjunction_v` 深度解析
这两个模板是C++17引入的类型特征组合工具,用于构建更复杂的类型判断逻辑。下面我将从技术实现到实际应用进行全面剖析:
一、基本概念与C++引入版本
1. std::disjunction_v
(逻辑OR)
- 引入版本:C++17
- 功能:对多个类型特征进行逻辑或运算
- 等价形式:
T1::value || T2::value || ... || Tn::value
- 别名模板:
disjunction_v<T...>
是disjunction<T...>::value
的简写
2. std::conjunction_v
(逻辑AND)
- 引入版本:C++17
- 功能:对多个类型特征进行逻辑与运算
- 等价形式:
T1::value && T2::value && ... && Tn::value
- 别名模板:
conjunction_v<T...>
是conjunction<T...>::value
的简写
二、底层实现原理
1. disjunction
的可能实现
template<class...> struct disjunction : std::false_type {};
template<class B1> struct disjunction<B1> : B1 {};
template<class B1, class... Bn>
struct disjunction<B1, Bn...> : std::conditional_t<B1::value, B1, disjunction<Bn...>> {};
2. conjunction
的可能实现
template<class...> struct conjunction : std::true_type {};
template<class B1> struct conjunction<B1> : B1 {};
template<class B1, class... Bn>
struct conjunction<B1, Bn...> : std::conditional_t<B1::value, conjunction<Bn...>, B1> {};
关键特性:
- 短路求值:类似运行时
||
和&&
的行为 - 继承结果类型:保留第一个确定结果的特征类型
三、与传统方式的对比
C++11/C++14实现方式
// 使用std::integral_constant手动组合
template<typename T>
using is_arithmetic_or_enum = std::integral_constant<bool,std::is_arithmetic<T>::value || std::is_enum<T>::value>;
C++17新方式
template<typename T>
using is_arithmetic_or_enum = std::disjunction<std::is_arithmetic<T>, std::is_enum<T>>;
优势对比:
特性 | 传统方式 | C++17方式 |
---|---|---|
可读性 | 较差 | 更接近逻辑表达式 |
编译错误信息 | 难以理解 | 更清晰 |
短路求值 | 需要手动实现 | 自动支持 |
嵌套组合 | 复杂模板嵌套 | 线性参数列表 |
四、在Quill中的典型应用
1. 多类型条件判断
if constexpr (std::disjunction_v<std::is_arithmetic<Arg>,std::is_enum<Arg>,std::is_same<Arg, void const*>>)
{// 处理基础类型
}
2. 复合类型检查
else if constexpr (std::conjunction_v<std::is_array<Arg>,std::is_same<remove_cvref_t<remove_extent_t<Arg>>, char>>)
{// 处理字符数组
}
五、技术细节深入
1. 短路求值示例
using T = std::disjunction<std::is_pointer<int*>, // truesome_invalid_expression<void> // 不会被实例化
>;
static_assert(T::value); // 安全通过
2. 类型继承特性
using Result = std::disjunction<std::is_floating_point<float>, // 继承std::true_typestd::is_pointer<int> // 被短路跳过
>;
static_assert(std::is_same_v<Result, std::true_type>);
六、最佳实践
-
优先使用
_v
后缀:// 好 if constexpr (std::disjunction_v<T1, T2>)// 不如前者简洁 if constexpr (std::disjunction<T1, T2>::value)
-
组合复杂条件:
template<typename T> using is_loggable = std::disjunction<std::is_arithmetic<T>,std::conjunction<std::is_class<T>,has_log_method<T>> >;
-
错误消息改进:
static_assert(std::disjunction_v<is_std_string<T>, is_string_view<T>>,"T must be either std::string or std::string_view");
七、性能考量
- 零运行时开销:全部在编译期解析
- 编译速度:比手动嵌套模板更快(得益于短路求值)
- 代码生成:与手写条件等效的机器码
八、与其他特性的结合
1. 与if constexpr
组合
template<typename T>
void process(T val) {if constexpr (std::disjunction_v<is_arithmetic<T>, is_enum<T>>) {// 处理数值类型} else if constexpr (is_string_like<T>) {// 处理字符串}
}
2. 与概念(Concepts)对比(C++20)
// C++17方式
template<typename T, typename = std::enable_if_t<std::disjunction_v<is_arithmetic<T>, is_enum<T>>>>
void foo(T);// C++20方式
template<typename T>
requires std::disjunction_v<is_arithmetic<T>, is_enum<T>>
void foo(T);
九、历史演变
C++版本 | 类型特征发展 |
---|---|
C++11 | 引入基本的类型特征(type traits) |
C++14 | 添加_t 后缀别名模板(如remove_const_t ) |
C++17 | 引入disjunction /conjunction 和_v 后缀 |
C++20 | 概念(Concepts)提供更直观的约束表达 |
这些工具共同构成了现代C++强大的类型系统基础设施,使模板元编程更加直观和高效。在Quill这样的高性能库中,它们被广泛用于编译期类型分发和优化决策。