news 2026/4/18 1:03:26

C++ Two Phase Lookup导致的模板代码编译错误

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ Two Phase Lookup导致的模板代码编译错误

猜猜下面这段代码的输出是什么:

template <typename T>

struct Base {

void DoThings() {

std::cout << "A\n";

}

};

template <typename T>

struct Derived: Base<T> {

void Do() {

DoThings();

}

};

int main() {

Derived<int> d;

d.Do();

}

肯定有人会说是A,但实际上是编译错误:

test.cpp: In member function 'void Derived<T>::Do()':

test.cpp:9:17: error: there are no arguments to 'DoThings' that depend on a template parameter, so a declaration of 'DoThings' must be available [-Wtemplate-body]

9 | DoThings();

| ^~~~~~~~

test.cpp:9:17: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)

给的报错信息很让人迷惑,因为DoThings是明确声明定义在Base<T>中的,这里居然在说它未被声明。

这其实是c++的Two Phase Lookup导致的。

Two Phase Lookup如其字面意思,对于任何模板代码,编译器需要进行两次检查:

Phase 1,第一步检查,只检查模板代码是否有语法错误,但涉及到和模板类型参数相关的部分会跳过。检查的范围包括是否有明显的语法错误比如用了不存在的关键字、少了分号等,其中也会检查那些和模板类型参数无关的函数、类型、方法是否已经被声明,这和编译器检查普通代码的流程很相似

Phase 2,这一步会往模板的参数里带入实际的类型,编译器会重新推导整个模板代码在当前的类型下是否合法

两步骤是为了更快速地将类型参数不相关的问题排除,这样在保证模板代码语法正确性的同时尽量保证了泛型代码的灵活性,理想中也能让模板的编写者更快发现问题而不是把问题延迟到类型推导之后。

但坏处就是会让模板产生一下诡异的编译错误了,比如上面的DoThings。DoThings在这里是非限定名称,但没有参数,同时它也和Derived模板的类型参数不直接相关,这导致对DoThings的检查会在Phase 1执行,而Phase 1会忽略所有的模板参数相关内容,这导致Base<T>在这时不可见,而我们又没有在其他地方定义DoThings,所以编译器认为我们在使用一个未声明的符号,于是报了语法错误。

解决方法也很简单,让DoThings和类型参数相关即可,或者通过this去调用,this代表了泛型模板类自身,也算和模板参数相关:

template <typename T>

struct Derived: Base<T> {

void Do() {

- DoThings();

+ this->DoThings();

+ // Base<T>::DoThings(); 也可以

}

};

另外如果我们提供了自由函数DoThings,那么在Phase 1中就会把对应的名字认定为是在调用自由函数,这时编译器不再报错,但Base<T>的方法永远调用不到了:

template <typename T>

struct Base {

void DoThings() {

std::cout << "A\n";

}

};

void DoThings() {

std::cout << "B\n";

}

template <typename T>

struct Derived: Base<T> {

void Do() {

DoThings();

}

};

int main() {

Derived<int> d;

d.Do(); // 输出B,自由函数DoThings被调用

}

这很违反直觉,因为普通的非模板子类在这种时候会去基类的作用域里寻找同名的方法,但因为Two Phase Lookup,编译器在Phase 1把函数绑定到了全局的自由函数上,这导致了非预期的结果。

总结

模板会有Two Phase Lookup做两遍检查,因此它和普通的代码行为上会有区别。

除了上面说的让方法和模板参数关联,其他补救措施还有很多,一种在GCC给出的编译报错里:加上-fpermissive启用permissive模式。在这个模式下不会进行Two Phase Lookup,模板会在实例化的时候再做检查,可以避免报错,但实测gcc-15上无法避免错误调用自由函数的问题。另外permissive模式会大幅改变语言和编译器的行为,贸然启用会出现很多意外问题。因此这一措施我并不推荐。

让方法名和模板参数相关也不能解决所有问题,因为还有很多时候我们需要利用非限定名称来自动选取合适的函数/方法,碰到这种情况就只能特殊场景特殊处理了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 6:58:40

阿里Wan2.1开源:1.3B参数打破视频生成垄断,消费级GPU即可运行

阿里Wan2.1开源&#xff1a;1.3B参数打破视频生成垄断&#xff0c;消费级GPU即可运行 【免费下载链接】Wan2.1-T2V-1.3B-Diffusers 项目地址: https://ai.gitcode.com/hf_mirrors/Wan-AI/Wan2.1-T2V-1.3B-Diffusers 导语 阿里巴巴通义实验室开源的Wan2.1-T2V-1.3B模型…

作者头像 李华
网站建设 2026/4/18 2:02:47

3个步骤解决FunASR时间戳对齐问题:从新手到精通的完整指南

3个步骤解决FunASR时间戳对齐问题&#xff1a;从新手到精通的完整指南 【免费下载链接】FunASR A Fundamental End-to-End Speech Recognition Toolkit and Open Source SOTA Pretrained Models, Supporting Speech Recognition, Voice Activity Detection, Text Post-processi…

作者头像 李华
网站建设 2026/4/17 2:54:19

Qwen3-4B智能客服升级方案:3步实现企业级AI对话降本增效

Qwen3-4B智能客服升级方案&#xff1a;3步实现企业级AI对话降本增效 【免费下载链接】Qwen3-4B-MLX-4bit 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen3-4B-MLX-4bit 在数字化转型浪潮中&#xff0c;智能客服已成为企业提升用户体验的核心竞争力。通义千问Q…

作者头像 李华
网站建设 2026/4/18 1:12:50

改善深层神经网络 第二周:优化算法(三)Momentum梯度下降法

1. Momentum 梯度下降法1.1梯度下降中的“震荡”现象我们用课程里的图来看一下这个问题&#xff1a;Pasted image 20251110104620现在假设这就是我们的网络的损失图像&#xff0c;我们通过一次次迭代&#xff0c;让损失下降到最低点。这里展开两个问题&#xff1a;&#xff08;…

作者头像 李华
网站建设 2026/4/18 2:03:24

音乐管理|基于springboot + vue音乐管理系统(源码+数据库+文档)

音乐管理系统 目录 基于springboot vue音乐管理系统 一、前言 二、系统功能演示 详细视频演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 基于springboot vue音乐管理系统 一、前言 博主介绍…

作者头像 李华
网站建设 2026/4/18 0:06:52

强化学习训练监控实战:从噪声曲线到可靠指标的诊断指南

你是否曾在训练强化学习模型时&#xff0c;面对看似随机波动的奖励曲线无从下手&#xff1f;当训练日志中充斥着-100到1000的奖励值时&#xff0c;如何判断模型是在进步还是在退化&#xff1f;本文将从工程实践角度&#xff0c;为你构建一套完整的训练监控诊断体系&#xff0c;…

作者头像 李华