首页 / 技术细节 / 可移植性 / ETI |
在 C++ 模板问题的上下文中,ETI 是“Early Template Instantiation”(早期模板实例化)的缩写——这是 Microsoft Visual C++ 特有的一个问题,在 Microsoft 平台上的模板开发中曾是严重的障碍,直到 Microsoft 开发者在 Visual C++ 7.1 (2003 .NET) 中修复了这个问题。尽管如果通过代码库系统性地应用正确的技术,这个问题相对容易规避,但这种方法肯定会非常繁琐且耗时。所以,如果您有一天发现自己花费了太多时间来处理这个问题,请考虑升级到新版本的编译器。事实上,无论如何都应该认真考虑这一点。节省时间、金钱和减少挫败感的收益是物有所值的。
下面是 MSVC 6.x 中该问题的一个简短演示。
template< typename F, typename T > struct apply1 { typedef typename F::template apply<T>::type type; };
编译这段看似无害的代码时,我们会得到
portability.cpp(4) : error C2903: 'apply' : symbol is neither a class template nor a function template portability.cpp(5) : see reference to class template instantiation 'apply1<F,T>' being compiled portability.cpp(4) : error C2143: syntax error : missing ',' before '<' portability.cpp(5) : see reference to class template instantiation 'apply1<F,T>' being compiled portability.cpp(4) : error C2059: syntax error : '<' portability.cpp(5) : see reference to class template instantiation 'apply1<F,T>' being compiled
诊断信息中的“symbol is neither a class template nor a function template”(符号既不是类模板也不是函数模板)部分,实际上经常是 ETI 相关问题的指示。另一个典型的错误消息通常会说一些关于嵌套类型 such-and-such 不是全局命名空间成员的内容。
这两种情况都是同一个编译器 bug 的两个方面,我们称之为“早期模板实例化”:编译器出于内部目的,为了处理类模板定义,会使用虚拟模板参数(int)来实例化类模板。这可能发生在模板定义解析期间(此类错误最容易识别和修复——模板定义本身就无法编译;上面的例子就属于这种情况),或者稍后在模板实例化期间,而后者很难检测——bug 只会在某个特定上下文中触发。
ETI 始终在命名空间范围的模板定义解析期间执行,这基本上意味着任何被模板参数替换后变得无效的模板定义(用int)可能会无法编译,就像我们的例子一样
template< typename F, typename T > struct apply1 { // typedef typename F::template apply<T>::type type; // ETI generates this: typedef typename int::template apply<int>::type type; };
如果您编译这段代码,您将得到完全相同的诊断信息,就像我们刚才看到的。
请注意,我们说的是“可能无法编译”,因为……好吧,简短的回答是,“这取决于”。我们还没有分析到可以告诉您 ETI 导致错误的确切条件以及何时不会,但无论如何这并不重要——如果是一个错误,您只需修复它(我们一会儿会展示如何做),如果不是,那就保持原样。如果有一天潜在的问题变成了一个实际的问题,那么您就应用我们即将给出的解决方法。
我们已经看过典型的诊断信息了,所以不再重复。我们只想提一下,许多 MSVC 的内部编译器错误(ICEs)实际上是由实例化堆栈深处某个 ETI 相关问题引起的。
在这种情况下,我们无法改变编译器的行为,所以我们必须适应它,并仍然让我们的模板按照我们想要的方式工作。令人惊讶的是,在大多数情况下,这是很容易实现的
// potentially unsafe template< typename F > struct apply0 { typedef typename F::type type; }; // now ETI-safe template<> struct apply0<int> { typedef int type; };
由于原始模板从未可能用int实例化,提供一个占位符int特化是完全无害的。