为什么某些编译器会坚持要求模板基类的成员公共成员,而非模板类不需要相同?请查看以下代码清单:
模板类:
#include <iostream>
using namespace std;
template <class T>
class TestImpl {
public: // It wont make a difference even if we use a protected access specifier here
size_t vval_;
TestImpl(size_t val = 0) : vval_(val) { }
};
template <class T>
class Test : public TestImpl<T> {
public:
Test(size_t val) : TestImpl<T>(val) {
cout << "vval_ : " << vval_ << endl; // Error: vval_ was not declared in this scope
//! cout << "vval_ : " << TestImpl<T>::vval_ << endl; // this works, obviously
}
};
int main() {
Test<int> test1(7);
return 0;
}
非模板类:
#include <iostream>
using namespace std;
class TestImpl {
public: // It wont make a difference even if we use a protected access specifier here
TestImpl(size_t val = 0) : vval_(val) {}
size_t vval_;
};
class Test : public TestImpl {
public:
Test(size_t val) : TestImpl(val) {
cout << "vval_ : " << vval_ << endl;
}
};
int main() {
Test test1(7);
return 0;
}
上述代码清单之间的显着差异是,虽然第一个清单使用模板类,但第二个清单没有。
现在,两个列表都可以使用 Microsoft的Visual Studio编译器(cl)进行编译,但第一个列表 WONT 使用 Digital Mars Compiler (dmc)和极简主义GNU for Windows (MinGW - g ++)编译器。我会得到一个错误,例如“vval_未在范围内声明” - 这个错误我很明白这意味着什么。
如果我使用 TestImpl&lt; T&gt; :: vval _ 限定访问 TestImpl 的公共变量 vval _ ,则代码可以正常运行。在第二个清单中,当派生类访问基类“ vval _ ”变量而没有限定它时,编译器不会抱怨。
关于这两个编译器以及可能的其他编译器,我的问题就是为什么我应该能够直接从非模板类继承访问(强制) vval _ 变量。一个非模板类,虽然我不能从从模板类继承的模板类做同样的事情?
答案 0 :(得分:6)
您必须使用vval_
限定TestImpl<T>
,告诉编译器它取决于T
中Test<T>
的实际类型(可能有部分/显式)在TestImpl<T>
的定义之前声明的Test<T>
的特化以及它将在该上下文中改变vval_
的含义的实例化。为了使编译器意识到这一点,你必须告诉它vval_
依赖于(模板参数)。
答案 1 :(得分:3)
MSVC(微软......)在模板代码方面从未符合标准,所以他是奇怪的:)
问题是模板分两个阶段进行解析:
在您的情况下,第一阶段失败,因为vval_
未明确依赖于模板参数(不是依赖名称),因此它应该可用。
一个简单的补救措施是通过vval_
来限定this->
,以将其标记为依赖。
答案 2 :(得分:2)
您面临的问题是,编译器vval_
不是依赖名称,因此它会尝试在具有该类型的模板的实际实例化之前查找它。此时,编译器 [*] 尚不知道基类型,因此它不考虑模板化基数。 Visual Studio不执行两阶段查找,因此不需要这样做。
解决方案是将标识符转换为依赖标识符,这可以通过多种方式之一完成。最简单和建议使用this
(如this->vval_
中所述)。通过添加显式this
,编译器知道vval_
可以根据模板参数而不同,它现在是依赖名称,并且它将查找推迟到第二阶段(在论证替换之后)。
或者,您可以使用TestImpl<T>::vval_
限定标识符所属的类型,如@mrozenau所建议的那样。同样,这使得标识符依赖于模板参数T
并且查询被推迟。虽然它们都服务于将查询推迟到以后的最终目的,但是第二种方法具有额外的副作用,即动态调度将被禁用。在这种特殊情况下,无关紧要,但如果vval_
实际上是虚函数,那么this->f()
将调用最终覆盖,而TestImpl<T>::f()
将执行TestImpl<T>
中存在的覆盖
[*] 在模板的第一阶段验证期间,在将参数替换为模板之前,基本类型尚不可知。原因是不同的参数集可能会触发选择基本模板的不同特化。