当前位置: 首页 > news >正文

CppCon 2014 学习:Modern Template Metaprogramming A Compendium

理解说明:
“Modern Template Metaprogramming: A Compendium” 这个标题通常指的是一组 现代 C++ 模板元编程(Template Metaprogramming,TMP) 技术、工具和模式的合集。

什么是 Template Metaprogramming?

模板元编程是使用 C++ 的模板系统,在编译期执行逻辑、计算类型、生成代码结构的一种编程方式。它常用于:

  • 编译期类型检查
  • 条件编译逻辑
  • 代码生成与优化
  • 泛型算法
  • 静态反射

“Modern” 表示什么?

现代 TMP 与早期 TMP(例如 C++98/03 中的 boost::mpl)相比,有许多改进:

旧 TMP(C++98)现代 TMP(C++11/14/17/20)
boost::mpl、手动递归constexpr, if constexpr
手写元函数结构体template <typename...>auto 泛型 lambda
手动递归列表操作使用 std::tuple, std::integer_sequence
没有反射结构化绑定、concepts、模板约束
模板特化判断类型std::is_same, std::enable_if, std::conditional_t 等工具辅助

A Compendium:包含什么?

通常这样的“汇编”会介绍这些现代 TMP 工具和技巧:

类型工具(Type Traits)
  • std::is_same, std::is_base_of, std::is_integral, …
  • std::enable_if, std::conditional, std::decay, std::remove_cvref
constexpr + 模板逻辑
  • constexpr if
  • 编译期数组、查找表、递归
变参模板(Variadic Templates)
  • 操作类型包(template <typename... Ts>
  • 展开参数包
  • 元编程算法(例如 filter/map/fold)
类型列表(Typelist)
  • type_list<Ts...>
  • 与 fold/map/reduce 等组合
SFINAE & Concepts
  • 替代传统 SFINAE:requires, concept, constrain auto
  • 更好的错误信息与限制机制
模板 Lambda 与函数对象
  • auto lambda 参数(C++14 开始)
  • template<class T> constexpr auto F = [](T x){ ... };
静态反射(C++20/23 提案)
  • 反射类型名、字段、方法等结构(目前通过宏/工具模拟)

示例:变参计数器

template<typename... Ts>
constexpr std::size_t count = sizeof...(Ts);
static_assert(count<int, double, char> == 3);

示例:类型筛选器

template<typename... Ts>
using only_integrals = std::tuple<std::conditional_t<std::is_integral_v<Ts>, Ts, void>...>;

目标用途

现代 TMP 的应用领域包括但不限于:

  • 静态数据建模
  • 编译期 JSON / 配置解析
  • 高性能泛型库(如 ranges、fmt、protobuf)
  • 类型安全系统(serialization, variant, visitor)

总结

“Modern Template Metaprogramming: A Compendium” 是对 C++20/23 时代模板元编程技术的系统梳理,强调:

  • 简洁、类型安全
  • 编译期逻辑表达能力增强
  • 使用标准库 trait 和 constexpr 工具替代手写元结构
  • 可读性和可维护性显著提高

Abstract 解读(摘要)

这是一次关于 现代 C++ 模板元编程(Template Metaprogramming) 的技术讲座摘要,适合已经具备一定 C++ 基础的人群,特别是模板编程方面有经验的开发者。

讲座目的

“Template metaprogramming has become an important part of a C++ programmer’s toolkit.”

  • 模板元编程已经是现代 C++ 工程中不可或缺的一部分。
  • 本次演讲将:
    • 展示 最先进的 TMP 工具和技巧
    • 利用这些技术,重新实现标准库中一些常见功能(如 tuple, variant, optional 等)

技术亮点

“void_t, a recently-proposed, extremely simple new <type_traits> candidate…”

  • 特别提到了 void_t:这是一个极简的、但功能非常强大的 type_traits 工具,用于实现 SFINAE 检测(Substitution Failure Is Not An Error)——在模板中判断某个表达式是否合法。
  • void_t 的用法虽然简单,却被认为是“高级且优雅的”,甚至能让一些有经验的模板开发者感到惊喜。
template<typename, typename = void>
struct has_type_member : std::false_type {};
template<typename T>
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type {};
  • 上面这段就是利用 void_t 实现的经典 检测某类型是否有嵌套 type 成员 的技巧。

演讲结构

  • 分为 两部分,中间有一个简短的休息。
  • 内容涉及高阶 TMP 概念,不适合初学者。
    • 明确提示:不是为 C++ 新手准备的!
    • 如果你是初学者,这次可能“跳级”了,但未来可以再来。

总结

项目内容
目标展示现代 TMP 技术,尤其是可用于实现标准库机制的技术
特点包括 void_t 等精简而强大的技巧
注意高阶内容,不适合新手 C++ 开发者

如果你希望我帮你 解释 void_t 的底层原理、用例场景,或者希望我复刻一个标准库功能(比如用 TMP 实现一个简化版的 optionalvariant

关于讲者本人(A little about me)

这段内容是讲者的自我介绍,展示了其专业背景、行业经验以及在 C++ 领域的深厚积累。

教育背景

  • 本科:数学(B.A. in Mathematics)
  • 硕士 & 博士:计算机科学(M.S., Ph.D. in Computer Science)

专业经历

  • 从事专业编程工作接近 50 年
  • 1982 年起使用 C++ 编程(C++ 发布初期)
  • 在多个领域拥有实际经验:
    • 工业界
    • 学术界
    • 顾问咨询
    • 科研机构

教育与领导经验

  • 创立了一个计算机系(Founded a CS Dept.)
  • 曾担任教授和系主任
  • 教授并指导各层次学生

管理与培训经验

  • 管理并指导销售渠道的编程团队
  • 作为软件顾问和商业培训师在全球讲课
  • 曾在**Fermilab(费米国家实验室)**科学计算部门任职,专注 C++ 和内部咨询

当前状态

  • 已退休(from Fermilab)
  • 但仍然 可以提供咨询服务(Not dead!)

总结

内容说明
学术背景数学本科 + 计算机科学硕博
编程经验近 50 年,C++ 使用超过 40 年
多重角色教授、主管、顾问、讲师、开发者
机构背景Fermilab 科学计算部门
当前状态退休但仍可接项目或顾问工作

讲者在 C++ 标准化领域的地位与贡献

1. 资深(Emeritus)C++ 标准化参与者
  • 曾是 C++ 标准委员会 WG21 的活跃成员
  • 现在是 Emeritus(荣誉)参与者,意味着已不再担任正式委员,但其贡献和地位仍被认可
2. 撰写过 85+ 标准提案论文
  • 这些论文为 C++ 标准引入了多项重要库特性,比如:
    • cbegin / cend:用于获取容器的常量迭代器
    • common_type:用于推导多个类型的共同类型
    • 以及完整的 <random><ratio> 头文件实现
3. 深刻影响了核心语言特性
  • 贡献包括但不限于:
    • 别名模板(alias templates)
    • 上下文转换(contextual conversions)
    • 变量模板(variable templates)
4. 重要标准项目编辑角色
  • 担任过 ISO/IEC 29124 国际标准(数学特殊函数的 C++ 标准)的项目编辑
  • 现为 C++17 标准的副项目编辑(Associate Project Editor)
5. 强烈且专业的观点
  • 基于丰富的培训和实践经验,讲者持有一些较为强烈的观点
  • 这些观点不一定被所有程序员认同,但讲者认为它们应该被接受和采纳

总结

重点说明
标准委员会资深成员参与 WG21,撰写了 85+ 提案
库功能贡献cbegin/cend, common_type, <random>, <ratio>
语言特性贡献别名模板、上下文转换、变量模板
标准项目编辑ISO 29124 项目编辑,C++17 副项目编辑
专业观点有独特且坚定的编程方法论观点

预览的示例内容(无特定顺序)

1. 来自标准库 (std::) 的典型元编程工具
  • 类型常量和类型判断

    • integral_constant:封装常量值的类型
    • true_type, false_type:分别表示布尔真值和假值的类型
    • is_same:判断两个类型是否相同
    • is_void:判断类型是否为 void
    • is_integral:判断是否为整数类型
    • is_floating_point:判断是否为浮点数类型
    • is_signed:判断是否为有符号类型
  • 赋值能力检查

    • is_copy_assignable:类型是否可拷贝赋值
    • is_move_assignable:类型是否可移动赋值
  • 类型修饰符去除

    • remove_const:移除 const
    • remove_volatile:移除 volatile
    • remove_cv:移除 constvolatile
  • 条件类型和启用技巧

    • conditional:基于条件选择类型
    • enable_if:SFINAE 技巧,启用或禁用模板实例化
  • 迭代距离

    • distance:计算迭代器范围长度
2. 尚未进入标准库(或不一定有)的工具
  • 数学相关

    • abs:绝对值(常见但未必在元编程中)
    • gcd:最大公约数
  • 自定义类型检查和常量

    • type_is:判断类型类别(自定义)
    • bool_constant:类似于 integral_constant<bool, ...>
    • is_one_of:判断一个类型是否属于给定类型列表
    • void_t(重要且高级的工具):用于检测模板中的合法类型成员
    • has_type_member:检查类型是否包含特定成员类型
    • is_valid:SFINAE 友好的检查模板表达式是否合法
    • is_complete:检查类型是否已完全定义(非不完整类型)

总结

类别示例工具功能简介
标准库元编程工具integral_constant, is_same, enable_if类型判断、条件选择、SFINAE等
非标准(或自定义)void_t, is_one_of, has_type_member高级模板检测、类型列表判断

什么是模板元编程(Template Metaprogramming)?

定义(来源 Wikipedia)
  • **元编程(Metaprogramming)**是编写计算机程序的一种方式,这些程序:
    1. 写出或操作其他程序(或者它们自身)作为数据,或者
    2. 在编译期执行某些工作,而不是在运行时执行
C++ 中的模板元编程
  • C++ 模板元编程利用 模板实例化 机制,在编译时完成计算和逻辑推断。
  • 当代码中出现一个模板名字,且期待是函数、类型或变量时,编译器会根据模板参数生成(实例化)对应的实体。
举例
  • 函数模板调用:f(x),其中 f 是一个函数模板,编译器会在编译时根据 x 的类型实例化 f
优点

模板元编程的目的是为了:

  1. 提高源码的灵活性 — 让代码更通用,更容易适应不同类型和需求。
  2. 提高运行时性能 — 把一些本应运行时执行的计算提前到编译期,减少运行时开销。

这是2001年针对 std::pow() 函数和两个模板版本 pow<> v1pow<> v2 的性能测试结果:

函数real 时间user 时间sys 时间
std::pow()11.858 秒11.837 秒0.020 秒
pow<> v18.081 秒8.081 秒0.020 秒
pow<> v23.035 秒3.024 秒0.030 秒

备注:

  • 测试在 700MHz PIII CPU、Windows 2000 上进行。
  • 使用 gcc 2.95.2 编译器,且关闭了优化(-O0)。
  • 每个测试运行 10,000,000 次,并且重复 50 轮。

结论:

  • 模板版本 pow<> 显著快于标准库的 std::pow()
  • pow<> v2 性能最佳,显示了模板元编程通过在编译期计算,可以极大提升运行时效率。

这是2001年针对启用优化编译(g++ -O2)后,std::pow() 与两个模板版本 pow<> v1pow<> v2 的性能测试结果:

函数real 时间user 时间sys 时间
std::pow()11.857 秒11.847 秒0.020 秒
pow<> v14.286 秒4.236 秒0.010 秒
pow<> v20.300 秒0.190 秒0.010 秒

备注:

  • 使用了 g++ -O2 优化等级编译,其他条件相同。
  • 性能提升显著:
    • pow<> v1 比未优化时快了约 47%
    • pow<> v2 比未优化时快了约 90%

结论:

  • 编译器优化大幅提升了模板版本的性能,尤其是 pow<> v2,达到了极高的效率。
  • 说明模板元编程结合现代优化可以极大减少运行时开销。

当进行模板元编程时…

  • 要记住: 运行时(run-time) ≠ 编译时(compile-time)
    因为模板元编程是在编译阶段执行的,所以不能依赖:
    • 可变性(mutability)
    • 虚函数(virtual functions,不能动态绑定)
    • 运行时类型信息(RTTI)等运行时特性
  • 简单来说:
    运行时代码是在程序运行时执行的,而模板元编程是在程序编译时执行的。
    所以,模板元编程是在编译期写代码让编译器帮你做计算或生成代码,这限制了你能使用的语言特性。

如何将工作移到编译期(compile-time)

例子:一个计算整数绝对值的编译时元函数(metafunction)

template<int N>  // 模板参数 N 作为元函数的参数
struct abs {static_assert(N != INT_MIN);  // C++17 风格的编译期断言,防止溢出static constexpr auto value = (N < 0) ? -N : N;  // 计算并“返回”结果
};
  • 这里用模板参数 N 传入整数。
  • 使用 static constexpr 定义 value,它在编译时即被求值。
  • static_assert 用来保证 N 不是 INT_MIN(因为 -INT_MIN 会溢出)。

使用方式

int const n = ...;  // 或者直接声明为 constexpr
auto x = abs<n>::value;  // 模板实例化,得到一个编译时常量
  • 把参数作为模板参数传入。
  • 通过 abs<n>::value 获取编译时计算结果。
  • 这种写法使得绝对值计算在编译时完成,运行时直接使用结果,提升效率。

总结:
模板元编程让你写类似“函数”的模板,模板参数代表输入,value 代表输出,编译器在编译阶段就算好结果,运行时无需额外计算。

这段内容讲的是C++11中constexpr函数与模板元函数(metafunction)两者的异同和优劣。

C++11 constexpr 函数示例

constexpr auto abs(int N) {return (N < 0) ? -N : N;
}
  • 这是一个可以在编译时执行的函数(如果参数是编译时常量)。
  • 使用方式很直观,就是普通函数调用:abs(n)
  • 如果nconstexpr或编译时已知的常量,abs(n)会在编译时求值。

模板元函数的优势(相比constexpr函数)

模板元函数(结构体模板)除了能表达类似功能外,还有更多灵活强大的能力:

  • 公有成员类型声明:可以定义typedefusing类型别名,用来输出某种类型信息。
  • 公有静态成员数据static conststatic constexpr变量,可表示常量数据。
  • 公有成员函数声明和定义:可以定义constexpr成员函数,进一步封装行为。
  • 成员模板和编译期断言:支持嵌套模板和static_assert进行编译期检查,增强安全性和灵活度。

总结

  • constexpr函数写起来更自然,调用更方便,适合简单的编译时计算。
  • 模板元函数结构体更“元”,不仅能计算值,还能定义类型、成员函数、断言等复杂编译时逻辑,是元编程更强大的工具。
    两者各有用武之地,视场景需求而选择。

这是一个典型的使用模板元编程实现编译时递归计算最大公约数(GCD)的例子,利用了模板的偏特化作为递归终止(基底)条件。

代码解析

// 主模板,计算 gcd<M, N>,递归调用 gcd<N, M % N>
template<unsigned M, unsigned N>
struct gcd {static constexpr auto value = gcd<N, M % N>::value;
};// 偏特化,终止条件 gcd<M, 0>,此时结果为 M
template<unsigned M>
struct gcd<M, 0> {static_assert(M != 0, "gcd(0,0) undefined");static constexpr auto value = M;
};
  • 主模板定义了递归步骤:gcd<M, N> = gcd<N, M % N>,类似欧几里得算法的递归形式。
  • 偏特化模板定义了递归终止条件:当第二个参数是0时,最大公约数是第一个参数M,并且加了静态断言避免gcd(0,0)的非法情况。
  • 编译器在实例化模板时会根据参数匹配递归展开,最终在编译期算出结果。

使用示例

constexpr auto val = gcd<48, 18>::value;  // val == 6

val在编译时就能被计算为6。

这正是模板元编程的精髓:

  • 编译时计算
  • 递归展开
  • 偏特化匹配基底

这个例子展示了如何写一个**元函数(metafunction)**来计算数组类型的维度(rank),即数组的“层数”。

代码解析

// 主模板,处理非数组类型,rank为0(基底情况)
template<class T>
struct rank {static constexpr size_t value = 0u;
};// 偏特化,匹配数组类型 U[N]
template<class U, size_t N>
struct rank<U[N]> {static constexpr size_t value = 1u + rank<U>::value;
};
  • 主模板用于非数组类型,返回rank=0。
  • 偏特化匹配数组类型,rank是数组的第一层1,再加上对数组元素类型U递归调用rank。
  • 这样对多维数组int[10][20][30]rank会递归展开三层,结果是3。

使用示例

using array_t = int[10][20][30];
constexpr size_t r = rank<array_t>::value;  // r == 3

r 是编译期常量,表示数组的维度。

总结

  • 这是典型的模板元编程递归例子
  • 利用偏特化来匹配数组类型,递归计算属性
  • 运行时无需任何代码开销,全部在编译期完成

这是在讲 C++ 中 模板元函数(metafunction) 的另一个强大用途 —— 在编译期生成类型(type transformation)

要点总结:

这个例子展示了如何定义一个元函数 remove_const,它在编译期“去除”类型中的 const 修饰符,并返回一个新类型。

代码讲解:

1. 主模板(通用情况)
template< class T > 
struct remove_const  {  using type = T;  
};
  • 如果传入的类型 T 没有 const 修饰,就直接返回它自己。
  • type 是这个元函数的“返回值”。
2. 偏特化(专门处理 const 的情况)
template< class U > 
struct remove_const< U const >  {  using type = U;  
};
  • 如果传入的是 U const,就返回 U(即去掉 const)。

使用示例:

remove_const<int>::type         a; // a 的类型是 int
remove_const<const int>::type   b; // b 的类型也是 int(const 被去掉)

更现代的写法(C++14 起):

使用别名模板(alias template)更方便:

template<typename T>
using remove_const_t = typename remove_const<T>::type;
remove_const_t<const double> x; // x 是 double 类型

实用价值

  • 编译期间完成类型处理,无需运行时开销
  • 常见于泛型编程、模板库(比如 <type_traits>)。
  • 类似的还有:remove_reference, add_pointer, decay, is_same 等。

小补充

这个例子与标准库中的 std::remove_const 完全一样逻辑:

#include <type_traits>
std::remove_const<const int>::type x;      // x 是 int 类型
std::remove_const_t<const int>     y;      // C++14 简写

总结

元函数名返回类型用途
remove_const<T>一个结构体,里面定义了 type 成员编译期生成“去 const 的类型”
remove_const_t<T>typename remove_const<T>::type更简洁的别名,用于实际编程

这段内容讲的是 C++11 标准库元函数(metafunction)的一种命名约定(Convention #1),并举例说明如何通过这种约定实现一个 去除 volatile 限定符的类型变换模板

核心概念解释

什么是 Metafunction(元函数)?

  • 就是 在编译期间运行、用于计算或变换类型的模板结构(struct)。
  • 元函数通常有一个成员 type,表示其计算结果。

这个约定的内容是什么?

Convention #1:

如果一个模板元函数的结果是一个类型,则结果应命名为 type
这个规则在 C++11 开始普遍被采用(虽然早期如 std::iterator_traits 不遵守这个规则,因为它较早出现)。

示例:type_is —— 一个身份函数(identity metafunction)

template< class T > 
struct type_is { using type = T; 
};
  • 接受类型 T
  • 结果就是 T 自己。
  • 它就是 std::type_identity<T> 的自定义版本。

用它来实现 remove_volatile

// 一般类型:不是 volatile,直接继承 type_is<T>
template< class T > 
struct remove_volatile : type_is<T> { };// 特化:如果是 volatile 的类型,就返回不带 volatile 的版本
template< class U > 
struct remove_volatile<U volatile> : type_is<U> { };

这样做的好处:

  1. 避免重复写 using type = ...;
  2. 统一接口:所有类型元函数都遵循 ::type 访问结果。
  3. 可组合性好(多个元函数可以层层嵌套使用)。

使用示例:

remove_volatile<int>::type            a;  // a 是 int
remove_volatile<volatile double>::type b; // b 是 double

更简写(C++14 起):

template<typename T>
using remove_volatile_t = typename remove_volatile<T>::type;
remove_volatile_t<volatile int> c;  // c 是 int

总结表

模板元函数功能使用方式
type_is<T>恒等映射,返回类型 Ttype_is<T>::type
remove_volatile<T>去除类型中的 volatile 限定符remove_volatile<T>::type
remove_volatile_t<T>C++14 简写别名remove_volatile_t<T>

这段内容讲的是 编译期决策(compile-time decision-making) —— 通过模板元函数,在编译阶段根据布尔常量选择不同的类型或路径,从而构建自适应(self-configuring)代码

核心目标

定义一个模板元函数 IF 或别名 IF_t,作用相当于三元表达式:

p ? T : F

模板上下文中使用它来根据条件 p 选择一个类型:如果 ptrue,返回类型 T;否则返回类型 F

模板元函数实现

template<bool p, class T, class F>
struct IF : type_is<T> {};              // 默认选择 Ttemplate<class T, class F>
struct IF<false, T, F> : type_is<F> {}; // 特化选择 F// C++14 别名简化版本:
template<bool p, class T, class F>
using IF_t = typename IF<p, T, F>::type;

使用场景举例

int const q = ...;  // 用户配置参数// 1. 声明变量 k,类型为 int 或 unsigned(根据 q 的值)
IF_t<(q < 0), int, unsigned> k;// 2. 实例化并调用某个函数对象(F 或 G)
IF_t<(q < 0), F, G>{}(args...);// 3. 派生类 D,继承自 B1 或 B2
class D : public IF_t<(q < 0), B1, B2> { ... };

编译期决策的价值

  • 性能:没有运行时开销,因为所有决策在编译期已确定。
  • 可读性/可维护性:代码清晰表达「选择逻辑」。
  • 泛化/复用:适合用于模板库、配置选项、跨平台差异等。

延伸:标准库已有实现

标准库中其实早就有类似的东西:

#include <type_traits>std::conditional<p, T, F>::type

或 C++14 起的别名:

std::conditional_t<p, T, F>

你完全可以这样改写:

using IF_t = std::conditional_t<p, T, F>;

总结表

自定义名等价标准库说明
IF<p, T, F>std::conditional<p, T, F>返回 TF,基于布尔模板参数
IF_t<p, T, F>std::conditional_t<p, T, F>C++14 简写版本
是否需要我为你实现一个完整例子(带类继承或函数选择)来加深理解?

SFINAE(Substitution Failure Is Not An Error) 的基本原理,尤其是它如何在 模板的隐式实例化(implicit template instantiation) 过程中生效。

什么是 SFINAE?

SFINAE 是 C++ 模板机制中的一个核心特性,全称是:

Substitution Failure Is Not An Error
意思是:在模板参数替换失败时,不报错,而是忽略该模板候选项

编译器在模板实例化时做什么?

1. 推导模板参数(template argument deduction)

在使用模板时,编译器会尝试获取每个模板参数的具体类型。这些方式包括:

  • 显式指定:用户直接提供模板参数,如 func<int>()
  • 从函数参数推导:编译器从函数调用的实参中自动推断模板参数类型
  • 默认模板参数:模板定义中有 = default_type 的默认值

2. 替换模板参数

然后,编译器将这些具体类型 替换模板参数,并尝试生成实际代码:

template<typename T>
void func(T t) {typename T::type x;  // 可能出错
}

如果 T 没有 ::type 成员,那么 typename T::type 替换后就会是非法代码。

替换失败怎么办?

  • 如果替换后代码合法,模板就被实例化,正常使用。
  • 如果替换后代码不合法不是错误! 编译器只是将这个模板候选项悄悄丢弃,不会报错。
    这就是 SFINAE:替换失败不是错误,而是筛选机制的一部分

举个例子:简单演示 SFINAE

// 只有当 T 有成员 type 时,此模板才合法
template<typename T>
auto test(int) -> typename T::type;template<typename>
auto test(...) -> void;struct A { using type = int; };
struct B { };int main() {test<A>(0);  // 匹配第一个版本,因为 A 有 typetest<B>(0);  // 匹配第二个版本,因为 B 没有 type,SFINAE 生效
}

总结要点

步骤编译器行为
① 推导模板参数从调用上下文中获取具体类型
② 替换模板参数替换模板中所有参数位置
替换后合法?继续实例化,模板有效
替换后非法?被编译器“静默丢弃”而非报错 —— 这就是 SFINAE

SFINAE 的实际应用 ,使用 std::enable_if 来控制函数模板的实例化,从而选择不同版本的函数模板(即 条件重载)。我们来逐步解析。

目标

我们希望实现一个函数 f,它根据参数类型 T 的不同执行不同的实现:

  • 如果 T 是一个整数类型(如 intlong),就使用第一个实现。
  • 如果 T 是一个浮点数类型(如 floatdouble),就使用另一个实现。
  • 如果传入的类型既不是整数也不是浮点,比如 std::string,则函数无定义,编译失败(两个版本都被 SFINAE 过滤掉了)。

使用工具:std::enable_if

这是标准库中用于实现 SFINAE 的工具:

template<bool B, class T = void>
struct enable_if {};template<class T>
struct enable_if<true, T> { using type = T; };

简写形式(从 C++14 开始):

template<bool B, class T = void>
using enable_if_t = typename enable_if<B, T>::type;

代码解释

整数版本:

template< class T >
enable_if_t< std::is_integral<T>::value, int >
f ( T val ) {// 只在 T 是整数类型时,这个版本才会参与编译return val * 2;
}

浮点版本:

template< class T >
enable_if_t< std::is_floating_point<T>::value, long double >
f ( T val ) {// 只在 T 是浮点类型时,这个版本才会参与编译return val + 0.5;
}

示例用法

f(10);       // 调用整数版本,返回 int
f(3.14);     // 调用浮点版本,返回 long double
f("text");   // 错误:两个模板都被 SFINAE 去除了,编译失败

提示:如何改进?

可以提供一个通用的 fallback 版本,以避免完全无定义的调用:

template<typename T>
void f(T) {static_assert(std::is_integral<T>::value || std::is_floating_point<T>::value,"f() only supports integral or floating-point types");
}

这样,如果调用 f("text"),会得到一个更清晰的编译期错误信息,而不是“候选模板不匹配”的模糊提示。

Concepts Lite 的愿景及其如何简化当前复杂的模板元编程技术(如 SFINAE)的使用。我们逐步解析其含义,并用中文理解其重要性。

核心概念:Concepts Lite

Concepts Lite 是对 C++ 模板系统的一项重大增强,用于更自然地表达模板参数的约束条件。它使得模板接口更加清晰,并显著减少了 SFINAE 的冗长和不直观的语法。

对比 SFINAE vs Concepts

传统 SFINAE 写法

template<class T>
std::enable_if_t<std::is_integral<T>::value, int>
f(T val) {return val * 2;
}

优点:可实现类型约束
缺点:语法复杂、可读性差、编译错误难以理解

Concepts 写法(更现代、更清晰)

template<Integral T>
int f(T val) {return val * 2;
}

这里的 Integral 是一个 concept(概念),它大致相当于:

template<typename T>
concept Integral = std::is_integral_v<T>;

优点:

  • 可读性强:直接表达“此模板参数必须是整数类型”
  • 错误信息更友好
  • 简化模板编写和重载管理

历史背景

  • Concepts 概念起源于 C++ 模板的创始思想,主要由 Stepanov 推动(他也是 STL 的主要设计者)。
  • 概念的数学灵感来自德国著名数学家 Emmy Noether,她推动了抽象代数的发展,强调用代数结构的共性来统一理论。

未来展望(2025视角)

  • Concepts 已于 C++20 正式标准 中引入!
  • 使用场景广泛,如 STL 泛型算法、模板库开发、类型安全设计等。
  • 替代了很多以前用 enable_if 和 SFINAE 的代码,变得更清晰和易于维护。

小结:为什么 Concepts 很重要

特性传统 SFINAEConcepts(C++20)
可读性非常高
编写复杂度
编译错误提示难以理解明确简洁
模板约束表达能力间接直接表达(语义化)
如果你在学习现代 C++,掌握 Concepts 是理解模板设计的核心之一。

C++11 标准库元函数(metafunction)约定 #2,主要聚焦在“返回值”的表达方式。

C++11 元函数约定 #2:有值返回的 metafunction

规则说明

如果一个 metafunction 的返回值是一个(value),而不是类型(type),它应该:

  1. 使用一个 static constexpr 成员 value 来表示返回结果
  2. (可选)提供一些方便的类型定义或函数

示范:std::integral_constant

这是 C++11 中最具代表性的“值型元函数”结构:

template< class T, T v >
struct integral_constant {static constexpr T value = v;// 提供两个操作符重载:支持隐式转换和函数调用语法constexpr operator T() const noexcept { return value; }constexpr T operator()() const noexcept { return value; }// 其他成员通常不常用
};

示例用途

using five_t = std::integral_constant<int, 5>;
static_assert(five_t::value == 5, "Error");
five_t f;
int x = f();    // 相当于 x = 5;
int y = f;      // 隐式转换:相当于 y = 5;

这个结构的妙处在于:

  • 可以作为类型 使用:five_t 是一个类型
  • 也可以作为值 使用:five_t::value 是一个编译时常量值
  • 支持表达式语法:调用 f() 或赋值 f 都可得到 5

派生技巧(Inheriting from integral_constant

通过从 integral_constant 继承,可以轻松创建自己的值型 metafunction,比如:

template<typename T>
struct is_void : std::integral_constant<bool, false> {};template<>
struct is_void<void> : std::integral_constant<bool, true> {};

这利用了 C++ 的继承机制:

  • 可以自动继承 value 成员
  • 可以使用 operator() 和隐式转换等特性
  • 避免重复定义模板接口

小结

特性说明
value编译期常量值的标准访问接口
operator T()operator()允许像值一样用 metafunction 类型
继承 integral_constant写值型 metafunction 的推荐方式
使用场景用于类型特征(type traits)、条件选择、SFINAE 等

介绍了一个更完整、更现代化(基于 C++11)的 rank 元函数(metafunction)实现,用于在编译期计算一个数组类型的“维度”(即 rank,数组有多少层嵌套维度)。

理解目标:求数组类型的维度 rank

比如:

int a[2][3][4];

这个数组的 rank 是 3。

核心思想:用模板偏特化和递归实现计数

基本模板(标量类型)作为递归的基线:

template< class T >
struct rank : std::integral_constant<std::size_t, 0u> {};

说明:

  • 如果传入的 T 不是数组(即标量),那 rank 为 0。
  • 它继承自 std::integral_constant,所以自动拥有 ::value 等便利接口。

偏特化 #1:有界数组(T[N]

template< class U, std::size_t N >
struct rank<U[N]> : std::integral_constant<std::size_t, 1u + rank<U>::value> {};

说明:

  • 识别类型是 U[N] 时(固定长度数组)
  • 把 rank 递归地加 1,再对 U 调用 rank,以统计更深层次

偏特化 #2:无界数组(T[]

template< class U >
struct rank<U[]> : std::integral_constant<std::size_t, 1u + rank<U>::value> {};

说明:

  • 识别类型是 U[](长度未知的数组,常用于函数参数中)
  • 一样加 1,继续递归

使用示例

using T1 = int;
using T2 = int[10];
using T3 = int[10][20];
using T4 = int[][30][40];static_assert(rank<T1>::value == 0, "T1不是数组");
static_assert(rank<T2>::value == 1, "T2是一维数组");
static_assert(rank<T3>::value == 2, "T3是二维数组");
static_assert(rank<T4>::value == 3, "T4是三维数组(首维无界)");

总结

模板形式意义
template<typename T> struct rank标量类型,rank 为 0
template<typename U, size_t N> struct rank<U[N]>有界数组类型,rank 加 1
template<typename U> struct rank<U[]>无界数组类型,rank 加 1
该写法利用了:
  • 模板递归
  • 偏特化匹配不同的数组形式
  • std::integral_constant 提供一致的接口(::value

你提到的内容主要讲的是 integral_constant 及其衍生工具在 C++ 模板元编程中的便利性,以下是简要解析:

integral_constant 是什么?

它是一个用于在编译期传递常量值的模板结构:

template<class T, T v>
struct integral_constant {static constexpr T value = v;using value_type = T;using type = integral_constant;  // type identityconstexpr operator value_type() const noexcept { return value; }constexpr value_type operator()() const noexcept { return value; }
};

常见别名:

template<bool b>
using bool_constant = integral_constant<bool, b>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;

这些别名是许多类型特征(如 is_voidis_integral 等)背后的基础构建块。

编译期调用方式演变:

以下 4 种方式都能从类型特征中提取布尔值:

方式含义
is_void<T>::value传统方式,取静态成员值
bool(is_void<T>{})创建临时对象并转换为 bool(C++11)
is_void<T>{}()创建对象并调用其 operator()(C++14)
is_void_v<T>变量模板(C++14 起),更简洁(C++17 标准)

示例

#include <type_traits>
#include <iostream>
template<bool B>
using bool_constant = std::integral_constant<bool, B>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
int main() {std::cout << std::boolalpha;std::cout << std::is_void<void>::value << '\n';     // truestd::cout << bool(std::is_void<int>{}) << '\n';     // falsestd::cout << std::is_void<void>{}() << '\n';        // true#if __cplusplus >= 201703Lstd::cout << std::is_void_v<void> << '\n';          // true
#endif
}

你提到的内容展示了 通过继承 + 偏特化的方式实现元函数(metafunction),这是 C++ 模板元编程中的常见技巧。以下是对两个例子的详细解释:

示例 1:判断类型是否为 void

// 基础模板:默认不是 void
template <class T>
struct is_void : false_type {};// 针对 void 的几个 cv 修饰版本做特化处理
template <> struct is_void<void> : true_type {};
template <> struct is_void<void const> : true_type {};
template <> struct is_void<void volatile> : true_type {};
template <> struct is_void<void const volatile> : true_type {};

原理:

  • 利用全特化,识别 void 及其所有 cv 修饰版本。
  • 继承自 true_type / false_type(本质上是 integral_constant<bool, true>),表示布尔值。
  • 元函数调用示例:
    static_assert(is_void<void>::value);           // OK
    static_assert(!is_void<int>::value);           // OK
    

示例 2:判断两个类型是否相同(is_same

// 默认两个类型不一样
template <class T, class U>
struct is_same : false_type {};// 如果两个类型相同,偏特化匹配成功
template <class T>
struct is_same<T, T> : true_type {};

原理:

  • 利用偏特化,当两个模板参数完全一致时,选择 is_same<T, T>,继承 true_type
  • 否则使用默认模板,继承 false_type
  • 示例:
    static_assert(is_same<int, int>::value);       // true
    static_assert(!is_same<int, float>::value);    // false
    

总结关键点

技巧用途
模板继承提供值类型元函数返回值
偏特化 / 全特化进行“模式匹配”来选择不同实现
true_type/false_type简洁表达布尔类型元数据

你展示的是 类型萃取(type traits) 中一种 类型别名的高级用法,目的是通过 别名模板(alias template)= 类型委托 + 类型绑定 的方式实现更简洁、更易组合的元函数。

逐步解析:

目标:判断某类型是否为 void

template<class T>
using is_void = is_same<remove_cv_t<T>, void>;

这个别名的含义是:

  • 去除 T 的 const 和 volatile 限定符后,
  • 判断该类型是否与 void 完全相同,
  • 返回结果为 is_same<去cv之后的T, void>(继承自 true_typefalse_type)。

中间步骤定义:

1️remove_cv:去除 const 和 volatile 限定
template<class T>
using remove_cv = remove_volatile<remove_const_t<T>>;
  • 这表示:先用 remove_const_t<T> 去除 const,再交给 remove_volatile<> 去除 volatile。
  • 返回的是一个类型(::type 还未取出)。
remove_cv_t:取出最终类型
template<class T>
using remove_cv_t = typename remove_cv<T>::type;
  • 这是标准 C++14 风格:为 remove_cv.type 添加简洁别名。
  • 这样你可以直接写 remove_cv_t<T> 而不是 typename remove_cv<T>::type

使用示例:

#include <type_traits>// 基础实现
template<class T>
using is_void = std::is_same<std::remove_cv_t<T>, void>;// 测试
static_assert(is_void<void>::value, "void");
static_assert(is_void<const void>::value, "const void");
static_assert(!is_void<int>::value, "int");

总结核心概念

名称说明
aliasing使用 using A = B<X> 实现类型别名
delegation将实际逻辑委托给已有类型(如 is_same
binding利用别名 A<T> = B<处理后的T> 将类型“绑定”到最终实现
这种模式的优势是:可读性高、组合性强、编译期效率高,C++14 之后在 <type_traits> 中被广泛使用。

理解说明:Dispatching to best-performing algorithm

这段内容讲的是如何利用**类型萃取(type traits)模板重载(function overloading)**在编译期选择最佳的算法实现,以提高性能。这种技巧也称为 tag dispatching(标签调度)

背景:std::distance 的性能依赖于迭代器类型

C++ 中有不同种类的迭代器,每种的功能和性能差异很大:

迭代器类别是否支持 e - b(随机访问)典型时间复杂度
Input / Forward不支持O(N)
Random Access Iterator支持 e - bO(1)

用 tag dispatching 区分两种实现:

// 适用于随机访问迭代器:可以用 e - b 实现 O(1)
template<class Iter>
auto distance(Iter b, Iter e, std::true_type) {return e - b;
}// 适用于非随机访问迭代器:必须遍历来计数,O(N)
template<class Iter>
auto distance(Iter b, Iter e, std::false_type) {typename std::iterator_traits<Iter>::difference_type d = 0;for (; b != e; ++b) ++d;return d;
}

分发入口:根据迭代器类型自动选择实现

template<class Iter>
inline auto distance(Iter b, Iter e) {// 判断 Iter 是否是随机访问迭代器类型using tag = std::is_same<typename std::iterator_traits<Iter>::iterator_category,std::random_access_iterator_tag>;return distance(b, e, tag{});
}
  • is_same<...> 返回 true_typefalse_type
  • tag{} 会在编译期生成相应的类型对象
  • 然后根据 tag 选择合适的重载版本

这个模式的核心是:

步骤说明
1. 萃取类型信息使用 iterator_traits<Iter>::iterator_category
2. 编译期布尔判断is_same<...> 返回 true_typefalse_type
3. 标签调度用类型标签选择合适的函数实现(函数重载)

总结:为什么用这个技巧?

  • 编译期选择最优实现,不依赖运行时判断,更快更安全。
  • 可扩展性好,可以支持更多类型和策略。
  • 标准库广泛使用,如 std::advance, std::distance 等都用这种方式进行优化。

标准库常用的 迭代器标签分发(dispatching via iterator tags) 技术,目的是根据迭代器类型在编译期自动选择对应的算法实现,以实现性能优化。

关键点解析:

  • 每个迭代器类型都通过 std::iterator_traits<Iter>::iterator_category 关联一个标签类型(tag type),如:
    • std::random_access_iterator_tag
    • std::input_iterator_tag
    • 其他迭代器标签
  • 这些标签是空类型,仅用于类型匹配,方便通过函数重载选择实现。

代码示例说明:

template<class Iter>
auto distance(Iter b, Iter e, std::random_access_iterator_tag) {// 随机访问迭代器:直接用减法,O(1)return e - b;
}
template<class Iter>
auto distance(Iter b, Iter e, std::input_iterator_tag) {// 输入迭代器:只能顺序遍历,O(N)typename std::iterator_traits<Iter>::difference_type d = 0;for (; b != e; ++b) ++d;return d;
}
template<class Iter>
inline auto distance(Iter b, Iter e) {// 利用迭代器的类别标签调用合适的distance版本return distance(b, e, typename std::iterator_traits<Iter>::iterator_category{});
}

工作流程:

  1. 调用无标签版本 distance(b, e)
  2. 通过 iterator_traits 获取迭代器的类别标签
  3. 编译器根据标签调用对应重载函数实现

优点:

  • 编译期选择最优算法,避免运行时开销。
  • 代码结构清晰,易于扩展(比如支持其他迭代器类型)。
  • 标准库中广泛使用这种标签调度模式。

这是用模板参数包(parameter pack)实现的变体的 is_same,叫做 is_one_of,用来判断类型 T 是否和类型列表中的任意一个类型相同。

代码核心思路总结:

  • primary template 只声明接口,不定义行为。
  • base case 1:当类型列表为空,结果 false_type,表示不匹配任何类型。
  • base case 2:当列表头类型和 T 匹配,结果 true_type
  • 递归 case:当列表头类型和 T 不匹配,则递归检查列表剩余的类型。

完整示例代码:

#include <type_traits>// primary template: 只声明接口
template<class T, class... Types>
struct is_one_of;// base case: empty pack, no match
template<class T>
struct is_one_of<T> : std::false_type {};// match at head of list
template<class T, class... Tail>
struct is_one_of<T, T, Tail...> : std::true_type {};// mismatch at head, check tail recursively
template<class T, class Head, class... Tail>
struct is_one_of<T, Head, Tail...> : is_one_of<T, Tail...> {};

使用示例:

static_assert(is_one_of<int, char, int, float>::value, "int is in the list");
static_assert(!is_one_of<int, char, double, float>::value, "int is not in the list");

这段代码使用了刚才讲的 is_one_of 模板,将 is_void 实现为判断类型 T 是否属于四种 void 类型之一:

  • void
  • void const
  • void volatile
  • void const volatile

解释:

template<class T>
using is_void = is_one_of<T,void,void const,void volatile,void const volatile>;
  • 这里用的是 using 关键字创建类型别名。
  • is_void<T> 实际上是调用 is_one_of,传入 T 和 4 种 void 变体。
  • 如果 T 是其中任意一种,is_one_of 会继承自 std::true_type,否则继承自 std::false_type

使用示例:

static_assert(is_void<void>::value, "void is void");
static_assert(is_void<const void>::value, "const void is void");
static_assert(!is_void<int>::value, "int is not void");

你说的“unevaluated operands”(未求值操作数)指的是:sizeofalignoftypeiddecltypenoexcept 这些运算符的操作数表达式在编译时不会真正求值,也就是说:

  • 它们不会触发代码生成。
  • 只需要声明(declaration)就可以使用,不要求有定义(definition)。

举个关键用法例子:

#include <utility> // std::declvaltemplate<typename T>
auto foo(T&&) -> int;template<typename T>
using foo_return_t = decltype(foo(std::declval<T>()));
  • std::declval<T>() 是一个声明,模拟了类型 T 的右值。
  • decltype(foo(std::declval<T>())) 可以获取调用 foo 时返回类型,但不会真的调用 foo
  • 这样我们就可以在编译期推断类型,而无需实际构造对象或运行代码。

这个例子展示了如何用SFINAE检测一个类型 T 是否支持拷贝赋值操作

关键点解析:

template<class T>
using copy_assign_t = decltype(declval<T&>() = declval<T const&>());
  • 这是一个别名模板,用来表示 T 的拷贝赋值表达式的返回类型。
  • decltype 获取表达式 declval<T&>() = declval<T const&>() 的类型(拷贝赋值的返回类型)。
  • 如果表达式无效(即 T 不支持拷贝赋值),decltype 会导致替换失败(SFINAE)。
template<class T>
struct is_copy_assignable {
private:template<class U, class = copy_assign_t<U>>static true_type try_assign(U&&);static false_type try_assign(...);
public:using type = decltype(try_assign(declval<T>()));
};
  • 这是主模板结构体,检测 T 是否可拷贝赋值。
  • try_assign(U&&) 是一个模板函数,要求第二模板参数存在(即 copy_assign_t<U> 有效),如果有效返回 true_type
  • 另一重载 try_assign(...) 是捕获所有其他情况,返回 false_type
  • type 使用 decltype(try_assign(declval<T>())),即尝试调用 try_assign 来判断 T 是否支持拷贝赋值。

结论

  • 如果 T 支持拷贝赋值,try_assign(U&&) 匹配成功,typetrue_type
  • 否则,替换失败,try_assign(...) 匹配,typefalse_type

你说的这个“老技术”在 C++11 引入 decltype 之前非常常见,利用 sizeof 运算符的不求值特性,通过函数重载返回不同大小的类型来判断某个表达式是否有效。

具体步骤:

  1. 定义两个不同大小的类型

    typedef char (&yes)[1];  // 类型大小为1字节
    typedef char (&no)[2];   // 类型大小为2字节
    
  2. 重载检测函数

    • 当表达式合法时,返回类型为 yes
    • 当表达式非法时,调用变参函数重载,返回类型为 no
  3. sizeof 判断调用的重载版本

    sizeof(try_assign(...))
    
    • 如果返回 yes,大小是1,表示表达式有效。
    • 如果返回 no,大小是2,表示表达式无效。
  4. bool_constant 包装结果

    typedef bool_constant< sizeof(try_assign(...)) == sizeof(yes) > type;
    

这样就实现了一个编译期布尔值,用于判断表达式是否可用。

总结

  • 这是 C++11 decltype + SFINAE 之前的经典技巧。
  • 利用 sizeof 和函数重载实现编译期判断。
  • 典型于 C++98/03 元编程代码。

为什么 void_t 重要?它的作用是什么?

void_t 是一个非常简单但功能强大的辅助模板别名,通常用于SFINAE技巧中判断某些类型表达式是否有效。

  • 它的定义非常简单:
template<typename...>
using void_t = void;
  • 也有一种为了绕开 CWG1558 问题(编译器对未使用模板参数的处理不一致)而定义的更安全版本:
template<typename...>
struct voider { using type = void; };
template<typename... Ts>
using void_t = typename voider<Ts...>::type;

void_t 怎么帮我们?

它把任意类型参数模板包映射成 void,关键点:

  • 传入的模板参数(可能是某些类型表达式)在编译期被实例化检测合法性
  • 如果传入的模板参数类型不合法,则产生替换失败(SFINAE),触发模板重载排除。
  • 否则,void_t<...> 别名替换为 void

举个简单的例子

判断一个类型是否有成员类型 type

template<typename, typename = void>
struct has_type_member : std::false_type {};
template<typename T>
struct has_type_member<T, void_t<typename T::type>> : std::true_type {};
  • T::type 存在时,void_t<typename T::type> 合法,匹配偏特化,结果是 true_type
  • T::type 不存在时,偏特化替换失败,退回到主模板,结果是 false_type

总结

  • void_t 是编译期检查表达式合法性的惯用包装,简洁且强大。
  • 利用它,可以轻松写出通用的检测型 traits。
  • 在 C++17 标准中,void_t 已成为标准库的一部分。

你给的例子是 void_t 在检测类型成员是否存在的经典用法。
完整示范如下:

// ① 主模板,默认情况下没有 type 成员,继承 false_type
template<class, class = void>
struct has_type_member : std::false_type { };// ② 偏特化,当 T::type 合法时,使用 void_t 成功替换,继承 true_type
template<class T>
struct has_type_member<T, void_t<typename T::type>> : std::true_type { };

说明:

  • has_type_member<T> 默认是 false_type,表示没有 type 成员。
  • 偏特化中,void_t<typename T::type> 尝试提取 T::type,如果失败(T 没有 type),替换失败,SFINAE 规则作用,偏特化被排除。
  • 如果成功,则匹配偏特化,继承 true_type

使用示例

struct A { using type = int; };
struct B { };
static_assert(has_type_member<A>::value, "A has type member");  // 通过
static_assert(!has_type_member<B>::value, "B has no type member");  // 通过

这就是 void_t 搭配 SFINAE 实现成员检测的典型用法:

  • 主模板 has_type_member<T, void> 是默认版本,返回 false_type,表示类型 T 不含 type 成员。
  • 偏特化 利用 void_t<typename T::type>,若 T::type 合法,则该特化被启用,返回 true_type
  • SFINAE 机制保证如果 T::type 不存在,偏特化无效,编译器自动选择主模板。
    这种方式结构简洁,且不需要复杂的 sizeofdecltype 组合,非常适合现代 C++ 类型特征检测。

这里的设计思路是用 void_t 和 SFINAE 来检测类型 T 是否满足某种赋值操作(这里是“复制赋值”)的有效性:

详细解释:

  1. 辅助别名模板
template< class T >
using copy_assign_t = decltype( std::declval<T&>() = std::declval<T const&>() );
  • 该别名尝试生成表达式 T& = const T& 的类型(赋值表达式的返回类型)。
  • 如果此表达式是有效的,则 copy_assign_t<T> 是有效的类型,否则无效。
  1. 主模板(处理不满足复制赋值的类型):
template< class T, class = void >
struct is_copy_assignable : std::false_type { };
  • 默认情况下,类型不可复制赋值。
  1. 偏特化(处理满足复制赋值的类型):
template< class T >
struct is_copy_assignable< T, void_t< copy_assign_t<T> > > : std::true_type { };
  • copy_assign_t<T> 有效时,这个偏特化会被选中,继承自 true_type

小结:

  • SFINAE 原理:当 copy_assign_t<T> 无效,void_t<...> 替换失败,偏特化无效,编译器选择主模板(false_type)。
  • 当有效时,偏特化匹配更优,返回 true_type

扩展

如果你想检测 移动赋值,只需修改 copy_assign_t 中的右值引用版本:

template< class T >
using move_assign_t = decltype( std::declval<T&>() = std::declval<T&&>() );

完全理解!你这段代码是将检测“某种操作是否对类型有效”的SFINAE检测模式进一步抽象出来,用模板模板参数做通用操作包装,从而复用性更强。

* 模板模板参数 template<class> class Op 表示传入的是一个模板别名或类模板,接受一个类型参数,返回一个类型(或类型表达式)。

  • 主模板

    template<class T, template<class> class Op, class = void>
    struct is_valid : std::false_type { };
    

    默认情况下,类型 T 不满足操作 Op

  • 偏特化

    template<class T, template<class> class Op>
    struct is_valid<T, Op, void_t<Op<T>>> : std::true_type { };
    

    Op<T> 是一个有效类型时(即 Op<T> 可以被实例化),匹配此偏特化,说明 T 支持操作 Op

  • 应用示例

    template<class T>
    using is_copy_assignable = is_valid<T, copy_assign_t>;template<class T>
    using is_move_assignable = is_valid<T, move_assign_t>;
    

优点

  • 高度复用:只写一次 is_valid,通过传入不同的操作模板别名,检查不同的特性。
  • 简洁明了:代码更整洁,减少重复代码。

总结

这是一种典型的现代C++模板元编程设计范式,将操作检测封装成可传入的模板模板参数,再利用void_t和SFINAE做有效性检测,极大提高了代码通用性和可维护性。

这段内容展示了不一定非得用 void_t 来做 SFINAE,也可以用 enable_if_t 来达到类似效果。

具体分析

  • 主模板

    template<class T, class = void>
    struct is_signed : false_type {};
    

    默认假设所有类型不是有符号类型。

  • 偏特化

    template<class T>
    struct is_signed<T, std::enable_if_t<std::is_arithmetic<T>::value && (T(-1) < T(0))
    >> : true_type {};
    
    • 条件要求:
      1. T 是算术类型(整型或浮点型)
      2. 表达式 T(-1) < T(0) 为真 —— 这表明 T 是有符号类型(因为-1小于0)。
    • 只有满足这些条件,偏特化才成立,继承 true_type
  • 静态断言测试

    static_assert(is_signed<long>::value);
    static_assert(!is_signed<unsigned>::value);
    
    • long 是有符号类型,断言通过。
    • unsigned 不是有符号类型,断言也正确。

关键点

  • enable_if_t 也是一个 void 类型的别名模板,但它附带条件判断,只在条件成立时生效。
  • 当条件不满足时,这个偏特化被 SFINAE 排除掉,退回主模板。
  • 这种用法对写条件更灵活,特别是需要表达复杂逻辑时很方便。

总结

  • void_t 更简单,主要用作映射到 void,常用来检测类型成员存在与否。
  • enable_if_t 允许带条件,能更灵活地做“条件限定”。
  • 两者都可以用来实现 SFINAE,但用途和场景有所区别。

你这段讲的是SFINAE技巧不用依赖voidvoid_t也能实现,核心是在两个地方都出现同一个“占位类型”,但实现原理一样:

核心点总结

  • 需要同一个“占位类型”出现两处
    1. 主模板的默认模板参数(class = size_t
    2. 偏特化的第二个模板参数,使用依赖于T的类型表达式(decltype(sizeof(T))
  • 这里用的decltype(sizeof(T))
    • sizeof(T)在编译期求值,但如果T是不完整类型(比如void),sizeof(T)会编译错误。
    • SFINAE机制会令这偏特化不可行,选择主模板,结果为false_type

具体例子说明

template<class T, class = size_t>
struct is_complete : false_type { };template<class T>
struct is_complete<T, decltype(sizeof(T))> : true_type { };// 测试:
static_assert(is_complete<long>::value);   // long是完整类型
static_assert(!is_complete<void>::value);  // void是不完整类型
  • is_complete<long>会匹配偏特化,sizeof(long)合法,结果是true_type
  • is_complete<void>偏特化SFINAE失败,匹配主模板,结果是false_type

这套路和用void_tenable_if_t类似

  • 只是替换了“占位类型”(void替换成了size_tdecltype(sizeof(T)))。
  • 依然利用了SFINAE在模板参数替换阶段失败时“静默放弃”该偏特化。

void_t的标准化过程就是C++标准委员会讨论的事情:

  • **目标:**纳入C++17的<type_traits>,或者先以技术规范(TS)发布。
  • **反馈:**普遍认为void_t很优雅,是SFINAE编程里的“神助攻”。
  • **名字争议:**有很多候选名字,比如make_void_tas_void_tvoidify_talways_voidenable_if_valid等,反映出大家对名字的不同理解和偏好。

总结来说,void_t是一个专门用来把任意一组合法类型映射成void的模板别名,极大简化了SFINAE中的模板特化写法,也让元编程更直观、更易用。

  • 元函数成员类型和静态 constexpr 数据成员:用来储存和传递编译期计算的结果,比如 valuetype 等。
  • 元函数调用(递归可能)、继承、别名:把公共逻辑提取出来,减少重复,增强代码复用和可读性。
  • 模板特化(完全和部分):通过匹配模板参数模式,区分不同类型的处理方式,实现分类元编程。
  • SFINAE:用来优雅地排除不适用的模板候选,实现条件编译和重载决议。
  • 未求值操作数:比如 sizeofdecltypenoexcept 等,用来查询类型信息而不产生代码。
  • 参数包:表示类型列表的模板参数包,实现灵活的变长类型处理。
  • 标准库元函数(如 <type_traits> 中的)和经典元函数:提供基础设施和算法,比如类型判断、迭代器特性、数值限制等。
  • void_tis_valid 习惯用法:现代SFINAE写法的利器,使得检测类型特性更加简洁明了。

相关文章:

  • OpenLayers 图形交互编辑
  • Leetcode 1908. Nim 游戏 II
  • 数的划分--dfs+剪枝
  • 第3节 Node.js 创建第一个应用
  • vscode不满足先决条件问题的解决——vscode的老版本安装与禁止更新(附安装包)
  • 线程(上)【Linux操作系统】
  • 多类别分类中的宏平均和加权平均
  • Centos系统搭建主备DNS服务
  • pytest中的元类思想与实战应用
  • pytest 中 fixture 与类继承交互导致的问题
  • pytest 常见问题解答 (FAQ)
  • python批量解析提取word内容到excel
  • 【C++高级主题】命令空间(五):类、命名空间和作用域
  • 【Day41】
  • 零基础一站式端游内存辅助编写教程(无密)
  • ShenNiusModularity项目源码学习(32:ShenNius.Admin.Mvc项目分析-17)
  • UVa1457/LA4746 Decrypt Messages
  • python里面导入yfinance的时候报错
  • 小白的进阶之路系列之八----人工智能从初步到精通pytorch综合运用的讲解第一部分
  • tomcat yum安装
  • 可以做电商题目的网站/百度文库个人登录入口
  • 深圳网站建设优化排名/seo排名资源
  • 建立网站如何盈利/网址大全网站
  • 网站开发网站设计制作/seo霸屏软件
  • .net core 网站开发/软件开发外包
  • 前端是做网站吗/软文写作兼职