g++环境下C++模板派生类访问基类成员为何需显式使用this指针
这个行为是C标准规定的**两阶段名称查找(Two-phase name lookup)**规则决定的,所有符合C标准的编译器(包括g++、clang等)都会执行这个检查,并非g++独有的行为。
两阶段查找的基本逻辑
C++对模板中的名称查找分两个阶段执行:
第一阶段:模板本身被解析、还未传入具体类型参数完成实例化时,编译器只会查找不依赖任何模板参数的名称。这一阶段编译器不会进入依赖模板参数的作用域查找名称,因为模板特化的存在,编译器无法在这个阶段确定依赖模板参数的基类的具体结构——你完全可以针对某个特定类型给
Par写全特化版本,彻底改变基类的成员,比如:// 针对void类型的Par特化,根本不存在名为t的成员 template<> struct Par<void> {};如果编译器在第一阶段就假设
Par<U>一定包含成员t,后续遇到特化版本时就会出现逻辑矛盾,因此标准明确要求:非限定名称不会在依赖模板参数的基类作用域中执行查找。你写的模板派生类场景里,直接写t属于非限定名称,第一阶段在当前派生类作用域找不到t的定义,就会直接抛出编译错误。第二阶段:模板被传入具体类型参数、真正实例化的时候,所有依赖模板参数的名称才会被查找。这时候模板参数已经确定,依赖参数的基类(比如
Par<int>)的结构是完全明确的,编译器可以准确判断成员是否存在。
为什么非模板派生类没有这个问题
当派生类是普通类、继承的是已经具象化的模板基类(比如Par<int>)时,基类类型在解析派生类定义的时候就已经完全确定,不存在后续特化修改结构的可能,编译器可以直接扫描到基类的成员t,因此直接访问不会报错。
this->的作用
当你写this->t时,原本的非限定名t变成了依赖当前类实例的依赖名称(dependent name),编译器会将这个名称的查找推迟到第二阶段实例化时执行。这时候基类Par<U>的结构已经确定,自然可以找到成员t。
除了显式加this->,还有两种等效写法可以解决这个编译错误,本质都是把名称标记为依赖名,推迟查找时机:
- 在派生类内部用using声明显式引入基类成员:
template <typename U> struct Chl : public Par<U> { using Par<U>::t; void setT(U in) { t = in; } }; - 访问成员时显式指定基类作用域:
void setT(U in) { Par<U>::t = in; }
内容的提问来源于stack exchange,提问作者acalabash

