Koenig Lookup 有关问题, 难度: 8
Koenig Lookup 问题, 难度: 8C/C++ code#include iostreamusing namespace stdnamespace NS {class T {
Koenig Lookup 问题, 难度: 8
C/C++ code#include <iostream>using namespace std;namespace NS {class T { };void f(T){ cout <<"NS::f()" <<endl;}} void f(NS::T){ cout <<"Global::f()" <<endl;}NS::T parm; int main() { f(parm); // 为何发生 ambiguous?}
C/C++ code#include <iostream>using namespace std;namespace NS {class T { };void f(T){ cout <<"NS::f()" <<endl;}} void f(NS::T){ cout <<"Global::f()" <<endl;}NS::T parm; int main() { void f(NS::T); // 这里加了一个声明, 生命的是那个 f() ? f(parm); // 调用了哪个 f()?}
[解决办法]1 楼那些混淆视听的声明都是编译不过去的(g++-4.4.3,4.5.2,4.6.1,4.7.0),去掉他们以后 1 楼程序和主楼第一个例子相同,因此只要分析主楼程序即可。
主楼第一个例子中的二义性源于函数重载存在二义性。具体过程是,当编译器解析到 f(param) 这句时,编译器能够确定 f 是一个函数名字,因此编译器需要为 f 生成重载函数集合。因为 f 的具体用法为一个 unqualified name,所以首先执行 unqualified name lookup,此过程将 ::f 加入重载函数集合。然后编译器意识到 f(param) 的调用中 param 为一个类类型变量,根据现有条件,需要执行 argument-dependent lookup,后者将 NS::f 加入重载函数集合。因此最终的重载函数集合是 {::f,NS::f}。重载函数集合生成后,函数重载解析发生,因为 ::f 和 NS::f 的形参列表相同,且都能够匹配当前调用,因此重载解析无法决议(ambiguous),调用失败。
主楼第二个例子与第一个例子的区别在于,unqualified name lookup 后的现有条件(上段红色文字)两者不同。此时重载函数集合中包含的 f 是声明于 main 函数定义作用域中的函数。根据 C++11 标准,在这种情况下,argument-dependent lookup 不会向已有重载函数集合中加入任何东西。因此最终的重载函数集合为 {当前作用域中声明的 f},之后重载解析能够无异议的决议 f(param) 调用中涉及的 f 就是当前作用域中声明的 f,即 void f(NS::T); 后者刚好又是 ::f 的再次声明,因此最终调用 ::f。
另外当前作用域中声明的 f,不一定就非得是 ::f。比如主楼第二个例子中的 void f(NS::T) 也可以改成 void f (int); 此时,重载解析仍然会失败,不过是因为函数实参与形参无法匹配造成的。还可以在 main 函数中同时声明 void f (NS::T) 和 void f (int),此时仍会调用 ::f,因为重载解析能够自动选择 void f (NS::T);
最后一点需要说明的,上述蓝色文字部分中所述的行为是 C++11 标准中新定义的,旧的标准(C++03)中没有这一规定。因此你要是用一款旧的编译器去编译主楼第二个例子,其行为将和第一个例子一样。比如 g++-4.4.3 编译第二个例子的时候仍然会出现二义性(ambiguous)。