Skip to content

C++17 逻辑类型特征组合

概述

std::conjunctionstd::disjunctionstd::negation 是 C++17 在 <type_traits> 中引入的模板,用于逻辑组合类型特征。它们分别是逻辑与(&&)、逻辑或(||)和逻辑非(!)的编译期版本,支持短路求值——当结果已确定时跳过后续特征的实例化,避免编译错误和不必要的模板展开。

基本定义

cpp
#include <type_traits>

// conjunction:所有特征都为 true 时结果为 true(短路与)
template <class... Bs>
struct conjunction : std::true_type {};

template <class B>
struct conjunction<B> : B {};

template <class B, class... Bs>
struct conjunction<B, Bs...>
    : std::conditional_t<bool(B::value), conjunction<Bs...>, B> {};

// disjunction:任一特征为 true 时结果为 true(短路或)
template <class... Bs>
struct disjunction : std::false_type {};

template <class B>
struct disjunction<B> : B {};

template <class B, class... Bs>
struct disjunction<B, Bs...>
    : std::conditional_t<bool(B::value), B, disjunction<Bs...>> {};

// negation:取反
template <class B>
struct negation : std::bool_constant<!bool(B::value)> {};

基本用法

cpp
#include <type_traits>
#include <iostream>

int main() {
    // conjunction:所有特征必须为 true
    static_assert(std::conjunction<
        std::is_integral<int>,
        std::is_signed<int>,
        std::is_convertible<int, long>
    >::value, "all must be true");

    // disjunction:任一特征为 true 即可
    static_assert(std::disjunction<
        std::is_integral<int>,
        std::is_floating_point<int>
    >::value, "at least one must be true");

    // negation:取反
    static_assert(std::negation<
        std::is_pointer<int>
    >::value, "int must not be a pointer");

    std::cout << "all assertions passed\n";
}

短路求值

这是与直接使用 &&/|| 的关键区别:

cpp
#include <type_traits>

// 短路:当 is_integral<int> 为 true 时,不再实例化 is_same<int, int*>
// is_same<int, int*> 中的 int* 是不完整类型也不会导致问题
static_assert(std::conjunction<
    std::is_integral<int>,             // true → 继续
    std::is_same<int, int>             // true → 结果 true
>::value);

// 与 && 的对比:两者都会实例化所有类型
// 但 conjunction 在第一个 false 时就停止
static_assert(std::is_integral<int>::value &&
              std::is_same<int, int>::value);  // 两者都被实例化

短路的实际好处——避免 SFINAE 中的错误:

cpp
#include <type_traits>
#include <iostream>

// 问题:如果 T 不是类类型,&T::value 会导致编译错误
// 使用 conjunction 可以短路,避免不必要的实例化

// 不安全的写法(短路不保证)
// template <typename T>
// using safe_check = std::bool_constant<T::value && std::is_class<T>::value>;
// 如果 T 不是类类型,T::value 会先被实例化,导致错误

// 安全的写法(conjunction 短路)
template <typename T>
using safe_check = std::conjunction<
    std::is_class<T>,           // 先检查是否是类
    std::bool_constant<T::value> // 只有是类时才检查 T::value
>;

struct Good { static constexpr bool value = true; };
struct Bad { };

int main() {
    static_assert(safe_check<Good>::value);   // OK
    static_assert(!safe_check<int>::value);   // OK,短路跳过 T::value
    static_assert(!safe_check<Bad>::value);   // OK

    std::cout << "all safe checks passed\n";
}

辅助变量模板

C++17 提供了便捷的别名模板:

cpp
#include <type_traits>
#include <iostream>

int main() {
    // conjunction_v:直接获取 bool 值
    static_assert(std::conjunction_v<
        std::is_integral<int>,
        std::is_signed<int>
    >);

    // disjunction_v
    static_assert(std::disjunction_v<
        std::is_integral<double>,
        std::is_floating_point<double>
    >);

    // negation_v
    static_assert(std::negation_v<
        std::is_pointer<double>
    >);

    std::cout << "alias templates work\n";
}

实际应用场景

SFINAE 约束

cpp
#include <type_traits>
#include <iostream>
#include <string>

// 只接受整数或浮点类型
template <typename T>
std::enable_if_t<std::conjunction_v<
    std::is_arithmetic<T>,
    std::negation<std::is_same<T, bool>>
>, T>
safe_multiply(T a, T b) {
    return a * b;
}

// 只接受可移动但不可拷贝的类型
template <typename T>
std::enable_if_t<std::conjunction_v<
    std::is_move_constructible<T>,
    std::negation<std::is_copy_constructible<T>>
>, void>
process(T&& val) {
    T moved = std::move(val);
}

int main() {
    std::cout << safe_multiply(3, 4) << "\n";      // 12
    std::cout << safe_multiply(2.5, 4.0) << "\n";  // 10
    // safe_multiply(true, false);  // 编译错误:bool 被排除
}

模板特化

cpp
#include <type_traits>
#include <iostream>
#include <vector>
#include <list>

// 为随机访问迭代器容器提供优化的 at()
template <typename Container>
typename std::enable_if_t<
    std::conjunction_v<
        std::is_same<typename Container::iterator_category,
                     std::random_access_iterator_tag>,
        std::negation<std::is_const<Container>>
    >,
    typename Container::reference
> unsafe_at(Container& c, size_t i) {
    // 随机访问:O(1)
    return c[i];
}

template <typename Container>
typename std::enable_if_t<
    std::negation_v<
        std::is_same<typename Container::iterator_category,
                     std::random_access_iterator_tag>
    >,
    typename Container::reference
> unsafe_at(Container& c, size_t i) {
    // 非随机访问:O(n)
    auto it = c.begin();
    std::advance(it, i);
    return *it;
}

int main() {
    std::vector<int> v = {10, 20, 30};
    std::cout << unsafe_at(v, 1) << "\n";  // 20

    std::list<int> l = {100, 200, 300};
    std::cout << unsafe_at(l, 2) << "\n";  // 300
}

概念模拟(C++20 之前)

cpp
#include <type_traits>
#include <iostream>
#include <string>
#include <sstream>

// 模拟 C++20 concept:Printable
template <typename T>
using is_printable = std::conjunction<
    std::is_object<T>,
    std::negation<std::is_pointer<T>>,
    std::negation<std::is_array<T>>
>;

template <typename T>
std::enable_if_t<is_printable<T>::value>
smart_print(const T& val) {
    std::cout << val << "\n";
}

// 为指针提供特化
template <typename T>
std::enable_if_t<std::is_pointer_v<T>>
smart_print(T ptr) {
    if (ptr) {
        std::cout << *ptr << "\n";
    } else {
        std::cout << "(null)\n";
    }
}

int main() {
    smart_print(42);           // 42
    smart_print("hello");      // hello
    int x = 100;
    smart_print(&x);           // 100
}

conjunction 与 && 的对比

特性std::conjunction&&
求值时机编译期编译期
短路求值支持(跳过后续实例化)不保证(可能全部实例化)
错误处理短路避免 SFINAE 错误可能触发意外的编译错误
可读性模板元编程中更清晰简单场景更直观
返回类型std::bool_constantbool

编译器支持

编译器最低版本备注
GCC5.0完整支持
Clang3.5完整支持
MSVC19.0 (VS 2015)完整支持

注意conjunction/disjunction/negation 从 C++11 起就已在许多编译器中作为扩展提供,C++17 标准化后正式纳入。所有现代编译器均完整支持。

最佳实践

  • SFINAE 约束中优先使用:替代 &&/||/! 可以避免短路不一致导致的编译错误。
  • 使用 _v 后缀conjunction_v<Bs...>conjunction<Bs...>::value 更简洁。
  • 组合多个类型特征:创建复杂的类型约束,为模板特化提供精确控制。
  • 在 Concepts 之前提供约束:C++20 之前,这是实现概念模拟的主要工具。

常见陷阱

cpp
// 陷阱 1:conjunction 短路但 && 不短路
// conjunction 在第一个 false 时停止实例化
// && 可能实例化所有操作数(取决于编译器优化)

// 陷阱 2:类型特征的 value 成员
// conjunction 的模板参数必须是继承自 true_type/false_type 的类型
// 不能直接使用 bool
// std::conjunction<std::true_type, true>  // 编译错误!

// 陷阱 3:空参数列表
static_assert(std::conjunction<>::value);   // true(默认继承 true_type)
static_assert(!std::disjunction<>::value);  // false(默认继承 false_type)

// 陷阱 4:与 fold expressions 的对比
// C++17 fold expressions 也可以实现逻辑组合,但不提供短路
// template <typename... Bs>
// using conjunction_fold = std::bool_constant<(Bs::value && ...)>;
// 这不保证短路!

基于 MIT 许可发布