问题可能有两个方面:为什么我们首先需要两阶段查找,并且鉴于我们有两阶段查找,为什么在第一阶段对令牌的解释是固定的。第一个是更难回答的问题,因为它是语言中的设计决策,因此它有其优点和缺点,并且取决于你的立场,或者其他人会更有分量。
您感兴趣的第二部分实际上要简单得多。为什么,在具有两阶段查找的 C++ 语言中,令牌含义在第一阶段是固定的,不能在第二阶段被解释。原因是 C++ 有上下文文法,而记号的解释高度依赖于上下文。如果不在第一阶段确定标记的含义,您甚至不会知道首先需要查找哪些名称。
考虑一个稍微修改过的原始代码版本,其中文字 5 被一个常量表达式替换,并假设您不需要提供最后一次咬您的template
ortypename
关键字:
const int b = 5;
template<typename T>
struct Derived : public Base<T> {
void Foo() {
Base<T>::Bar<false>(b); // [1]
std::cout << b; // [2]
}
};
[1] 的可能含义是什么(忽略在 C++ 中这是通过添加typename
and来确定的事实template
)?
Bar
是一个静态模板函数,它接受一个bool
作为模板参数和一个整数作为参数。b
是指常数 5 的非依赖名称。*
Bar
是一个嵌套模板类型,它采用单个bool
作为模板参数。b
是在函数内部定义Derived<T>::Foo
但未使用的该类型的实例。
Bar
是一个类型的静态成员变量,X
对其进行比较,该比较operator<
采用 abool
并产生一个U
可以与operator>
整数进行比较的类型的对象。
现在的问题是我们如何在模板参数被替换之前(即在第一阶段)继续解析名称。如果我们在情况 1. 或 3. 中,则b
需要查找并且可以在表达式中替换结果。在第一种情况下产生您的原始代码:Base<T>::template Bar<false>(5)
,在后一种情况下产生operator>( operator<( Base<T>::Bar,false ), 5 )
. 在第三种情况(2.)中,第一阶段之后的代码将与原始代码完全相同:(Base<T>::Bar<false> b;
删除额外的()
)。
第二行 [2] 的含义取决于我们如何解释第一行 [1]。在 2. 情况下,它表示对 的调用operator<<( std::cout, Base<T>::Bar<false> & )
,而在其他两种情况下,它表示operator<<( std::cout, 5 )
。同样,其含义超出了第二个参数的类型,如 2. case name b
insideDerived<T>::Foo
是依赖的,因此它无法在第一阶段解决,而是推迟到第二阶段(它也会通过添加参数依赖查找的命名空间Base
和实例化类型)。T
如示例所示,标记的解释会影响名称的含义,进而影响其余代码的含义,哪些名称是依赖的,因此在第一个过程中还需要查找哪些内容阶段。同时,编译器确实在第一遍期间执行检查,如果在第二遍期间可以重新解释标记,那么在第一遍期间的检查和查找结果将变得无用(想象在第一遍期间) passb
已被替换为5
仅发现我们在情况 2。在第二阶段!),并且必须在第二阶段检查所有内容。
两阶段查找的存在取决于被解释的标记及其在第一阶段选择的含义。另一种方法是像 VS 一样进行单遍查找。
*我在这里简化了案例,在 Visual Studio 编译器中,它不实现两阶段查找,b
也可以是Base<T>
当前实例化类型的成员T
(即它可以是从属名称)