Item 47:使用 traits classes 表现类型信息
对齐 Effective C++ 第三版 Item 47:用 iterator_traits 主模板与 pointer partial specialization 统一获取类型信息,以 iterator category tags 对 advance 做编译期分派,并验证能力、复杂度和扩展边界。
学习目标
- 能解释 traits class 如何在不增加对象运行状态的前提下统一提供 class 与 raw pointer 的类型信息
- 能实现 iterator_traits 主模板、pointer partial specialization 和五类 iterator category tags
- 能设计 tag dispatch 版 advance,并验证不同能力的操作、负距离规则与复杂度保证
类型事实实验
先预测:这个 iterator 能保证什么?
先预测 traits 来源、category 和复杂度,再切换场景查看静态分派证据。
观察
泛型算法需要 category、value_type、difference_type 等 information about types,但 raw pointer 没有 nested members。
决策
用 traits classes 作为统一查询 facade;让 class iterator 由 primary template 提供信息,非侵入目标由 specialization 适配。
当前场景 · traits classes / information about types
用 traits classes 作为统一查询 facade;让 class iterator 由 primary template 提供信息,非侵入目标由 specialization 适配。
从“iterator 属于哪一类”开始
generic advance 需要把 iterator 向前或向后移动 distance。不同 iterator 能力差异很大:
- input iterator 只能逐步前进
- bidirectional iterator 可逐步前进和后退
- random-access iterator 可直接
+= distance
Item 47 的原则是 Use traits classes for information about types(使用 traits classes 表现类型信息)。
若算法只写循环,可支持所有 iterators,却浪费 random-access 的常量时间能力;若无条件写 +=,input iterator 无法编译。
先预测:运行期 if 检查 category 是否足够?两个 branches 仍需对当前 iterator 都是可编译 expressions,且类型事实本来在编译期已知。
iterator class 可以提供 nested category
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : input_iterator_tag {};
struct bidirectional_iterator_tag : forward_iterator_tag {};
struct random_access_iterator_tag : bidirectional_iterator_tag {};
template<class T>
class ListIterator {
public:
using iterator_category = bidirectional_iterator_tag;
};category tag 不是 enum runtime value,而是一个 type。继承层级表达能力包含:random-access 也满足 bidirectional、forward 和 input 的基本要求。
raw pointer 也是 iterator,但没有成员
int values[] = {1, 2, 3};
int* iter = values;
iter += 2;pointer 支持 random access,却不能为 int* 增加 iterator_category nested type。如果算法直接写 Iter::iterator_category,raw pointers 被排除。
解决方案是把查询移到独立 traits template。
iterator_traits 主模板
template<class Iter>
struct iterator_traits {
using iterator_category = typename Iter::iterator_category;
using value_type = typename Iter::value_type;
using difference_type = typename Iter::difference_type;
using pointer = typename Iter::pointer;
using reference = typename Iter::reference;
};generic code 只写 iterator_traits<Iter>::iterator_category,不关心信息来自 iterator 自身还是 specialization。
pointer partial specialization
template<class T>
struct iterator_traits<T*> {
using iterator_category = random_access_iterator_tag;
using value_type = std::remove_cv_t<T>;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
};iterator_traits<int*> 选择 pointer specialization,iterator_traits<ListIterator<int>> 选择 primary template,两者提供相同查询 names。
tag dispatch 的三个实现
template<class Iter, class Distance>
void doAdvance(Iter& iter, Distance n, random_access_iterator_tag) {
iter += n;
}
template<class Iter, class Distance>
void doAdvance(Iter& iter, Distance n, bidirectional_iterator_tag) {
if (n >= 0) while (n--) ++iter;
else while (n++) --iter;
}
template<class Iter, class Distance>
void doAdvance(Iter& iter, Distance n, input_iterator_tag) {
if (n < 0) throw std::out_of_range("negative distance");
while (n--) ++iter;
}forward iterator 的 tag 可转换为 input tag,random-access tag 也可转换到更弱 tags;overload resolution 选择最具体 exact match。
public wrapper 获取 trait 并分派
template<class Iter, class Distance>
void advance(Iter& iter, Distance n) {
using Category = typename iterator_traits<Iter>::iterator_category;
doAdvance(iter, n, Category{});
}客户只调用 advance;category 在编译期选择实现,没有 per-call virtual dispatch 或 runtime branch。
tag hierarchy 是能力关系,不只是名字列表
forward tag public 继承 input tag,bidirectional 继承 forward,random-access 继承 bidirectional。这让只提供弱能力 overload 的算法也能接受更强 iterator。
↡更强 category tag 可隐式转换为较弱 tag,从而复用基础实现的继承结构。output iterator 与 input 链表达不同写入语义,不应为了方便强行纳入同一可读层级。
↡类型声明的 tag 必须真实反映其 operations、multi-pass 与复杂度保证。若一个 iterator 宣称 random-access 却让 += 线性执行,程序可能结果正确但违反算法复杂度假设。
traits 不限于 types
traits 还可提供 compile-time constants:
template<class T>
struct serialization_traits {
static constexpr bool fixed_size = false;
using wire_type = T;
};标准库中的 numeric_limits、allocator_traits、char_traits 和 pointer_traits 都体现相似模式:一个统一查询 facade,背后由主模板和 specializations 提供事实。
用户 specialization 的所有权边界
并非所有 traits 都允许用户对任意标准类型添加 specialization。必须遵守该 traits 的定制契约;对自己拥有的 user-defined type 通常最安全。
↡谁有权为哪些类型提供 traits specialization 的 API 与标准规则。自定义 traits 应说明:primary template 是否有默认、缺少信息时报何种错误、用户 specialization 必须提供哪些 members。
现代 concepts 与 if constexpr
现代 ranges 使用 iterator concepts;算法可写:
template<class Iter, class Distance>
void modernAdvance(Iter& iter, Distance n) {
if constexpr (std::random_access_iterator<Iter>) {
iter += n;
} else if constexpr (std::bidirectional_iterator<Iter>) {
// loop both directions
} else {
// input path
}
}concepts 可直接检查 operations,减少类型谎报;但关联 types、policy constants 和第三方适配仍常通过 traits 提供。Item 47 的信息抽取与静态选择模型没有过时。
↡通过所需 expressions 和语义要求直接描述类型能力的命名约束。traits class 的设计流程
- 识别 generic algorithm 需要的类型事实。
- 为每项事实确定稳定 nested name 或 constant。
- 编写 primary template,决定转发、自省或无默认。
- 为 raw pointers 等不能自描述的类型提供 specializations。
- 用 tag overload、
if constexpr或 specialization 消费信息。 - 测试 class types、pointers、cv variants、边界操作与复杂度。
先预测 custom iterator、const pointer 和 input-only iterator 分别选择哪个 overload,再用 operation counter 验证 random-access 只执行一次 +=。
小结
- traits classes 在编译期提供关于 types 的统一信息,不需要修改目标类型或增加运行状态
- iterator_traits 主模板读取 class nested members,pointer partial specialization 适配 raw pointers
- category tags 用类型和继承表达 iterator capability hierarchy
- public advance 查询 trait 后进行 tag dispatch,在编译期选择最具体实现
- category 不只影响可编译性,还承诺 operations、multi-pass 和复杂度
- concepts 与 if constexpr 是现代消费方式,traits 仍适合关联类型、常量和非侵入适配
名词解释
本章出现的专业名词,用大白话再讲一遍。
- type information
- 泛型算法查询的类型属性。
- traits class
把类型映射为关联类型或常量的模板。
- iterator category tag
表示 iterator 能力的空类型。
- nested trait member
类型自身公开的关联属性名称。
- non-intrusive trait target
无法自行添加 nested members 的类型。
- traits primary template
处理常规类型的默认 traits 定义。
- partial specialization
为一族模板实参形状提供的具体定义。
- non-intrusive adaptation
不修改目标类型的 traits 适配。
- tag dispatch
用 tag 参数和重载选择静态实现。
- capability-specific overload
针对某类能力优化的内部函数。
- traits dispatch wrapper
查询 trait 并调用实现的公开薄层。
- capability tag hierarchy
强能力 tag 可转为弱能力 tag 的层级。
- category semantic contract
tag 对操作和复杂度的真实承诺。
- trait value member
traits 暴露的编译期常量。
- trait specialization ownership
traits 定制的类型所有权边界。
- if constexpr dispatch
按静态条件丢弃分支的选择。
- capability concept
直接描述有效操作的命名约束。
- traits design workflow
类型信息建模与消费流程。
练习
- 问题 1:traits classes 与 information about types。 让 advance 同时接受 ListIterator 和 raw pointer,两者分别是 bidirectional 与 random-access。
- 问题 2:iterator_traits 与 tag dispatch 的复杂度契约。 一个 iterator 把自己标成 random-access,但加等操作线性循环,分析后果与修复。
- 问题 3:traits classes、iterator_traits 与 tag dispatch 的非侵入适配。 为第三方不可修改 handle 类型提供 serialization trait,需要 wire type 与 fixed-size 常量。