• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2026-08-10 23:52 Aet 隐藏边栏 |   抢沙发  2 
文章评分 1 次,平均分 5.0

模板错误与调试

概述

  1. 模板报错之所以经常出现几百行,不是因为真正存在几百个错误,而是因为编译器必须告诉你:
    1. 最外层调用了哪个模板
    2. 它实例化了哪个内部模板
    3. 内部模板又实例化了什么
    4. 最终哪个表达式不合法

最小示例

  1. 下面代码不能编译

模板代码什么时候被检查

  1. 模板通常经历以下阶段:
阶段 主要工作
模板定义 检查语法和不依赖模板参数的名字
模板实参推导 从调用参数推导 T
替换与约束检查 将推导出的类型代入声明
模板实例化 检查依赖模板参数的代码
代码生成与链接 生成具体函数并解析符号
  1. 考虑

  1. write_log() 是非依赖名字
    1. 编译器在看到模板定义时就要能找到它
  2. serialize()依赖于 T,只有实例化时才能判断是否合法

诊断信息应该从哪里开始看

  1. 第一步:找最先出现的用户代码
  2. 第二步:找到实际模板参数
  3. 第三步:找最底层的第一个真实错误
  4. 第四步:阅读候选函数被拒绝的原因

typename 错误

  1. 考虑

  1. 编译器看到T::value_type,不能确定它是:
    1. 嵌套类型
    2. 静态数据成员
    3. 枚举值
    4. 其他名字

  1. 规则是
    1. 依赖于模板参数的限定名,如果要把它当作类型,通常需要在前面写 typename

template 消歧义

  1. 考虑

  1. 因为 object 的类型依赖于 T,编译器可能无法确定:
    1. <是模板实参列表的开始,还是小于运算符

  1. 规则是
    1. 在依赖对象或依赖限定名后调用成员模板时,可能需要使用 .template->template::template

依赖基类与this->

  1. Base<T> 是依赖基类
    1. 编译器在定义 Derived<T> 时不会直接到依赖基类中查找非依赖名字 run

两阶段名字查找

  1. 考虑

  1. 原因是
    1. 模板定义时,普通名字查找只看到 handle(long)</li> <li>handle(int) 在模板定义之后才声明
    2. 实例化时还会进行 ADL
    3. 但内置类型 int 没有关联命名空间,ADL 找不到后面的重载

SFINAE错误与硬错误

  1. 考虑

  1. 失败发生在函数模板声明的直接替换上下文中,因此该候选函数可以被移出重载集合
  2. 但如果失败发生在已经选中的函数体中

if constexpr 减少无效实例化

  1. 普通 if 的两个分支都必须合法

  1. T = int 时,虽然运行时不会进入 else,但
    1. value.size()仍然必须能够编译,所以会报错
  2. C++17 使用
    1. 未选择的分支不会针对当前 T 实例化

使用 static_assert 提前报错

  1. 假设一个线程池任务要求回调能够接受

  1. 可以在 API 边界检查

延迟触发 static_assert

  1. 模板中经常需要一个只有特定分支被实例化时才触发的错误

  1. 为什么不直接写
    1. C++17/C++20 中,直接的 false 可能在模板定义时就导致错误,即使这个分支从未被实例化

  1. C++23放宽了模板中的 static_assert(false),但为了兼容 C++17/C++20 编译器,工程中仍经常使用依赖类型的:

如何让编译器打印精确类型

  1. 方法一:故意实例化不完整类型

  1. 方法二:依赖 static_assert

  1. 方法三:编译器专用函数签名

variant 报错应该怎样看

  1. 假设

  1. 访问器漏掉 Heartbeat

C++20 Concepts 改善诊断

  1. C++17 可以使用

  1. C++20 可以把要求直接写出来

编译器诊断选项

  1. GCC 可以使用

选项 作用
-ftemplate-backtrace-limit=20 限制实例化回溯长度
-ftemplate-backtrace-limit=0 不限制,显示完整回溯
-fconcepts-diagnostics-depth=4 展开更多 Concepts 失败原因
-fno-elide-type 尽量不省略复杂类型
-fmax-errors=1 只看第一个错误,减少连锁噪声
  1. Clang 常用

  1. MSVC 推荐至少开启

C++20 Conceptsrequires

概述

  1. ConceptsC++20 的语言特性,用于直接表达模板参数必须满足的条件
  2. 它主要解决三个问题:
    1. 防止不满足要求的类型进入模板
    2. 改善模板错误信息
    3. 根据约束强弱进行重载排序

SFINAEConcepts

  1. C++17 中,约束“T 必须是整数”可能写成

  1. C++20 可以写成

  1. 区别不仅是语法更短
    1. Concepts 正式加入了模板重载决议和约束偏序,编译器能够理解“哪个模板约束更强”

最小可运行示例

  1. Concept

四种约束函数模板的写法

  1. 约束模板参数

  1. 前置 requires 子句

  1. 后置 requires 子句
    1. 常用于成员函数,因为约束可以靠近函数声明末尾

  1. 简写函数模板

requires 子句与 requires 表达式

  1. 这两个东西名字相同,但作用不同
  2. requires 子句
    1. 决定模板是否有资格参与重载

  1. requires 表达式
    1. 检查一组类型或表达式是否合法

四种 requirement

  1. requires 表达式中可以写四种要求:
  2. 简单要求

  1. 类型要求
    1. 使用 typename 检查某个嵌套类型是否存在

  1. 复合要求

  1. 嵌套要求

一个完整的 Range Concept

  1. 不过这个 Concept 仍然只是检查了一些语法,不能证明:
    1. begin()end() 表示同一个范围
    2. 迭代一定会结束
    3. size() 与元素数量一致
    4. 迭代器不会悬空
    5. 遍历一定是线程安全的
    6. size() 一定是 O(1)

标准库常用 Concepts

  1. 常见类型关系

  1. 常见对象能力

  1. 常见类型分类

  1. 可调用对象

  1. 赋值和交换

  1. 注意

same_asconvertible_to

  1. 考虑

Concept 使用条件
same_as<T> 类型本身是接口语义的一部分
convertible_to<T> 只需要能安全用于目标位置
简单要求 只关心表达式是否合法

约束参与重载排序

  1. Concepts 不只是移除非法候选,还可以判断哪个候选“更受约束”

  1. SmallIntegral<T> 包含 std::integral<T> 的全部要求,并增加了尺寸约束,因此它更具体

为什么推荐复用命名 Concept

  1. 下面两个重载看起来一个更严格

  1. 更可靠的方式是复用命名 Concept

requires 的短路行为

  1. 如果 T 没有 value_type,左侧约束为 false,右侧不再进行会产生硬错误的无效替换

万能引用中的 Concept 陷阱

  1. 下面的函数不一定接受整数左值

  1. 如果希望接受任意 cv/ref 形式的整数,应约束去掉引用后的类型

  1. 因此,写 Concept 时必须明确
    1. 约束的是调用表达式中的原始类型、引用类型,还是最终存储的衰变类型?

异步回调的工程 Concept

  1. 假设 async_receive() 会保存回调,并在收到数据后这样调用

  1. 可以定义

  1. 为什么约束std::decay_t<Handler>
    1. 因为真正保存到异步操作中的通常不是原始 Handler&&,而是衰变后的拥有型对象
    2. 去掉引用
    3. 去掉顶层 const
    4. 数组和函数发生退化

Concept 检查不了生命周期

  1. 下面的 lambda 满足 ReceiveHandler
    1. 但如果异步操作执行时 response 已经销毁,仍然会产生悬空引用

  1. 它不能证明:
    1. 引用捕获对象是否存活
    2. 回调是否线程安全
    3. 回调是否阻塞
    4. 回调是否会抛异常
    5. socket 是否已关闭
    6. 执行线程是否正确
    7. 回调是否只执行一次

Concept、SFINAE、static_asserttag dispatch

技术 作用
SFINAE 替换失败时移除候选
Concepts 声明约束、移除候选并参与重载排序
static_assert 候选已选中后给出硬错误
tag dispatch 根据编译期类别选择不同实现
if constexpr 在一个模板内部选择合法分支

  1. Concept 决定 send() 是否参与重载

不要过度约束

  1. 假设函数只需要

项目中的阅读方式

  1. 看到

  1. 检查约束顺序和是否存在更具体重载

  1. 区分外层约束子句和内层要求表达式

  1. 说明库关心的是底层对象类型

  1. 明库很可能准备按值保存或转发后保存对象

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2026-08-11
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享