当前位置: 代码迷 >> 综合 >> Argument Dependent Lookup (ADL, a.k.a. Koenig Lookup) 解析 (2)
  详细解决方案

Argument Dependent Lookup (ADL, a.k.a. Koenig Lookup) 解析 (2)

热度:32   发布时间:2023-12-16 19:41:54.0
Argument Dependent Lookup (ADL, a.k.a. Koenig Lookup) 解析 (1)

Roger( roger2yi@gmail.com.cn)

在说明顺序查找和Koenig查找如何共同作用的之前,先解释一下顺序查找,所谓顺序查找,就是从函数调用所处的域开始(如果函数调用处于一个成员函数中,初始域就是类域,如果处于自由函数中,初始域就是名字空间域或者全局域),依次由内到外到各个域进行名字查找,如果在某个域找到该名字的函数,就停止查找,将所有找到的重载函数进行重载决议,如果没有合适的候选者或者有多个合适的候选者而导致歧义,编译器则报错。如果一直找到全局域也没有找到任何该名字函数,编译器也报错。
例如:
namespace  KL
{
    
namespace KL_Inside
    
{
         
class KoenigLookup
         
{
         
public:
                 
void koenigLookup()
                 
{
                          KoenigLookupMethod();
                 }

         }
;
    }

}

KoenigLookupMethod的查找顺序依次是类KoenigLookup,名字空间KL::KL_Inside,名字空间KL,最后是全局域。

应该说,OL是名字查找的主要规则,只是在OL应用的某些阶段中KL也起作用,并将其作用附加在OL之上。

在继续阐述这一点之前,首先确认一个原则,类域比名字空间域(包括全局域)有更高的优先级,KL规则的作用范围是名字空间域里的自由函数,当OL应用于类域的成员函数的时候,KL是不起作用的。或者按照Herb的话说,成员函数与类之间的关系要比非成员函数更紧密(虽然都可以认为是类接口的一部分),当进行名字查找的时候,成员函数绝对不会跟非成员函数一起进行重载决议。

下面的例程说明了整个名字查找规则可能发生的各种各样的状况:

#include  < iostream >
using   namespace  std;
 
namespace  KL_ARG_2
{
    
class KoenigLookupArg2;
}

 
namespace  KL_ARG_1
{
    
class KoenigLookupArg
    
{
    }
;
 

    
//Overload method in namespace KL_ARG_1, same with KoenigLookupArg
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL_ARG_1::KoenigLookupMethod ";
    }

}

 
namespace  KL_ARG_2
{
    
class KoenigLookupArg2
    
{
    }
;
 

    
//Overload method in namespace KL_ARG_2, same with KoenigLookupArg2
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL_ARG_2::KoenigLookupMethod ";
    }

}

 

// Overload method is Global
void  KoenigLookupMethod(KL_ARG_1::KoenigLookupArg & ,
                                            KL_ARG_2::KoenigLookupArg2
& )
{

    cout
<<"Global KoenigLookupMethod ";
}

 
namespace  KL
{

    
//Overload method in namespace KL
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL::KoenigLookupMethod ";
    }

 
    
namespace KL_Inside
    
{
         
//Overload method in namespace KL::KL_Inside
         void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
                 KL_ARG_2::KoenigLookupArg2
&)
         
{

                 cout
<<"Namespace KL::KL_Inside::KoenigLookupMethod ";
         }

 
         
//Call overload method in the scope of namespace KL::KL_Inside
         void KL_KoenigLookup()
         
{
                 KL_ARG_1::KoenigLookupArg klArg;
                 KL_ARG_2::KoenigLookupArg2 klArg2;
                 KoenigLookupMethod(klArg, klArg2);
         }

 
         
class KoenigLookup
         
{
         
public:
                 
//Call overload method in the scope of class KoenigLookup
                 
//Non-Static member function
                 void koenigLookup()
                 
{
                          KL_ARG_1::KoenigLookupArg klArg;
                          KL_ARG_2::KoenigLookupArg2 klArg2;
                          KoenigLookupMethod(klArg, klArg2);
                 }

 
                 
//Call lookup method in the scope of class KoenigLookup
                 
//Static Member function
                 static void staticKoenigLookup()
                 
{
                          KL_ARG_1::KoenigLookupArg klArg;
                          KL_ARG_2::KoenigLookupArg2 klArg2;
                          KoenigLookupMethod(klArg, klArg2);
                 }

 
         
private:

                 
//Overload method in class KoenigLookup(Non-Static member function)
                 void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
                          KL_ARG_2::KoenigLookupArg2
&)
                 
{

                          cout
<<"Non-Static Member KL::KL_Inside::KoenigLookup::"
                                   
"KoenigLookupMethod ";
                 }

 

                 
//Overload method in class KoenigLookup(Static member function)
                 static  void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
                          KL_ARG_2::KoenigLookupArg2
&)
                 
{

                          cout
<<"Static Member KL::KL_Inside::KoenigLookup::"
                                   
"KoenigLookupMethod ";
                 }

         }
;
    }

}

 
 
int  main()
{

    
//1, Call overload method in the scope of class KoenigLookup(namespace KL)
    
//   Non-Static member function
    KL::KL_Inside::KoenigLookup kl;
    kl.koenigLookup();
 

    
//2, Call overload method in the scope of class KoenigLookup(namespace KL)
    
//   Static member function
    KL::KL_Inside::KoenigLookup::staticKoenigLookup();
 

    
//3, Call overload method in the scope of namespace KL
    KL::KL_Inside::KL_KoenigLookup();
 

    
//4, Call overload method in the scope of global
    KL_ARG_1::KoenigLookupArg klArg;
    KL_ARG_2::KoenigLookupArg2 klArg2;
    KoenigLookupMethod(klArg, klArg2);
 
 
    
char c;cin>>c;
    
return 0;
}

当然,上面的程序是不会被编译通过的,它是各种可能的组合镜像,我们用它删节之后的子版本来说明各种状况。

A
首先,我们来看重载函数
KoenigLookupMethod的调用发生在类KoenigLookup的成员函数koenigLookup(非静态)的情况。

#include  < iostream >
using   namespace  std;
 
namespace  KL_ARG_2
{
    
class KoenigLookupArg2;
}

 
namespace  KL_ARG_1
{
    
class KoenigLookupArg
    
{
    }
;
 

    
//Overload method in namespace KL_ARG_1, same with KoenigLookupArg
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL_ARG_1::KoenigLookupMethod ";
    }

}

 
namespace  KL_ARG_2
{
    
class KoenigLookupArg2
    
{
    }
;
 

    
//Overload method in namespace KL_ARG_2, same with KoenigLookupArg2
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL_ARG_2::KoenigLookupMethod ";
    }

}

 

// Overload method is Global
void  KoenigLookupMethod(KL_ARG_1::KoenigLookupArg & ,
                                            KL_ARG_2::KoenigLookupArg2
& )
{

    cout
<<"Global KoenigLookupMethod ";
}

 
namespace  KL
{

    
//Overload method in namespace KL
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL::KoenigLookupMethod ";
    }

 
    
namespace KL_Inside
    
{
         
//Overload method in namespace KL::KL_Inside
         void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
                 KL_ARG_2::KoenigLookupArg2
&)
         
{

                 cout
<<"Namespace KL::KL_Inside::KoenigLookupMethod ";
         }

 
         
class KoenigLookup
         
{
         
public:
                 
//Call overload method in the scope of class KoenigLookup
                 
//Non-Static member function
                 void koenigLookup()
                 
{
                          KL_ARG_1::KoenigLookupArg klArg;
                          KL_ARG_2::KoenigLookupArg2 klArg2;
                          KoenigLookupMethod(klArg, klArg2);
                 }

 
         
private:

                 
//Overload method in class KoenigLookup(Non-Static member function)
                 void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
                          KL_ARG_2::KoenigLookupArg2
&)
                 
{

                          cout
<<"Non-Static Member KL::KL_Inside::KoenigLookup::"
                                   
"KoenigLookupMethod ";
                 }

         }
;
    }

}

 
 
int  main()
{

    
//1, Call overload method in the scope of class KoenigLookup(namespace KL)
    
//   Non-Static member function
    KL::KL_Inside::KoenigLookup kl;
    kl.koenigLookup();
 
    
char c;cin>>c;
    
return 0;
}

上述程序是可以编译通过并运行的,输出结果是“Non-Static Member KL::KL_Inside::KoenigLookup::KoenigLookupMethod”,被调用的是类KoenigLookup的成员函数KoenigLookupMethod(非静态)。整个过程中KL规则并没有起作用,因为OL开始作用于类域,找到符合名字的成员函数之后就停止了查找,经过重载决议后得到最后调用的版本,类域中KL是不起作用的,我们把上面程序的调用函数和重载函数换作类的静态函数,结果也一样:

          class  KoenigLookup
         
{
         
public:
                 
//Call lookup method in the scope of class KoenigLookup
                 
//Static Member function
                 static void staticKoenigLookup()
                 
{
                          KL_ARG_1::KoenigLookupArg klArg;
                          KL_ARG_2::KoenigLookupArg2 klArg2;
                          KoenigLookupMethod(klArg, klArg2);
                 }

 

                 
//Overload method in class KoenigLookup(Static member function)
                 static  void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
                          KL_ARG_2::KoenigLookupArg2
&)
                 
{

                          cout
<<"Static Member KL::KL_Inside::KoenigLookup::"
                                   
"KoenigLookupMethod ";
                 }

         }
;
 
int  main()
{

    
//2, Call overload method in the scope of class KoenigLookup(namespace KL)
    
//   Static member function
    KL::KL_Inside::KoenigLookup::staticKoenigLookup();
 
    
char c;cin>>c;
    
return 0;
}

输出变成“Static Member KL::KL_Inside::KoenigLookup:: KoenigLookupMethod”。

如果成员函数KoenigLookupMethod不合适怎么办,比如它的签名被修改如下:                  


static   void  KoenigLookupMethod(KL_ARG_1::KoenigLookupArg & , KL_ARG_2::KoenigLookupArg2 & int )

编译器会直接报错说签名不吻合,并不会到后续的域继续进行查找,这也是所谓的name hiding名字隐藏。

B
继续考查KoenigLookupMethod调用发生在类KoenigLookup的成员函数中,但是在类KoenigLookup中没有找到任何候选者的情况,此时,根据OL,查找的域步进到了内层名字空间KL::KL_Inside中,如果在这个域找到了候选者,那么编译器此时就会附加KL规则,试图从KoenigLookupMethod的参数相关的域KL_ARG_1KL_ARG_2中查找更多候选者参加重载决议,例如:

#include  < iostream >
using   namespace  std;
 
namespace  KL_ARG_2
{
    
class KoenigLookupArg2;
}

 
namespace  KL_ARG_1
{
    
class KoenigLookupArg
    
{
    }
;
 

    
//Overload method in namespace KL_ARG_1, same with KoenigLookupArg
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL_ARG_1::KoenigLookupMethod ";
    }

}

 
namespace  KL_ARG_2
{
    
class KoenigLookupArg2
    
{
    }
;
 

    
//Overload method in namespace KL_ARG_2, same with KoenigLookupArg2
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL_ARG_2::KoenigLookupMethod ";
    }

}

 

// Overload method is Global
void  KoenigLookupMethod(KL_ARG_1::KoenigLookupArg & ,
                                            KL_ARG_2::KoenigLookupArg2
& )
{

    cout
<<"Global KoenigLookupMethod ";
}

 
namespace  KL
{

    
//Overload method in namespace KL
    void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
         KL_ARG_2::KoenigLookupArg2
&)
    
{

         cout
<<"Namespace KL::KoenigLookupMethod ";
    }

 
    
namespace KL_Inside
    
{
         
//Overload method in namespace KL::KL_Inside
         void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&,
                 KL_ARG_2::KoenigLookupArg2
&)
         
{

                 cout
<<"Namespace KL::KL_Inside::KoenigLookupMethod ";
         }

 
         
class KoenigLookup
         
{
         
public:
                 
//Call lookup method in the scope of class KoenigLookup
                 
//Static Member function
                 static void staticKoenigLookup()
                 
{
                          KL_ARG_1::KoenigLookupArg klArg;
                          KL_ARG_2::KoenigLookupArg2 klArg2;
                          KoenigLookupMethod(klArg, klArg2);
                 }

         }
;
    }

}

 
 
int  main()
{

    
//2, Call overload method in the scope of class KoenigLookup(namespace KL)
    
//   Static member function
    KL::KL_Inside::KoenigLookup::staticKoenigLookup();
 
    
char c;cin>>c;
    
return 0;
}

上面的程序会使编译器报错说对重载函数的调用不明确(VC 8.0),有三个可能性:

正在编译...

main.cpp

d:/devtest/learning/koeniglookup2/main.cpp(71) : error C2668: “KL::KL_Inside::KoenigLookupMethod”: 对重载函数的调用不明确

        d:/devtest/learning/koeniglookup2/main.cpp(56): 可能是“void KL::KL_Inside::KoenigLookupMethod(KL_ARG_1::KoenigLookupArg &,KL_ARG_2::KoenigLookupArg2 &)”

        d:/devtest/learning/koeniglookup2/main.cpp(30): “void KL_ARG_2::KoenigLookupMethod(KL_ARG_1::KoenigLookupArg &,KL_ARG_2::KoenigLookupArg2 &)”[使用参数相关的查找找到]

        d:/devtest/learning/koeniglookup2/main.cpp(16): “void KL_ARG_1::KoenigLookupMethod(KL_ARG_1::KoenigLookupArg &,KL_ARG_2::KoenigLookupArg2 &)”[使用参数相关的查找找到]

        试图匹配参数列表“(KL_ARG_1::KoenigLookupArg, KL_ARG_2::KoenigLookupArg2)”

值得注意的是,全局域和名字空间KL的重载函数KoenigLookupMethod并没有被编译器抱怨说是导致歧义的版本,因为这两个域此时根本不在查找的范围内。

C
假设在名字空间
KL_Inside里面找不到,那么查找的域继续步进到外层名字空间域KL,如果在该名字空间找到候选者,编译器一样附加KL规则从参数相关域KL_ARG_1KL_ARG_2中查找更多候选者参与重载决议。

D
如果
KL还是找不到,那么查找的域最后来到了全局域,因为OL不会再继续步进,所以编译器直接使用KL规则,在全局域和参数相关域KL_ARG_1KL_ARG_2中进行联合查找,将找到的候选者参与重载决议。

整个名字查找过程至此就结束了,如果还没有找到的话,编译器就会告诉你没有KoenigLookupMethod这个标识符。

最后看一下调用发生在非成员函数里面的情况:

如果KoenigLookupMethod调用发生在名字空间KL_Inside的自由函数中,情况与前述的B类似;
如果
KoenigLookupMethod调用发生在名字空间KL的自由函数中,情况与前述的C类似;
如果
KoenigLookupMethod调用发生在全局域的自由函数中,情况则与前述的D类似。

至此,函数的名字查找规则(包括OLKL)应该都解析的比较清楚了,当然,我们在编程中一般遇到的状况不会像上面几个例子那样那么复杂,实际上也不应该去搞得这么复杂,过于复杂的事物即使不会使我们犯错,也会使未来的我们代码的维护者犯错。但是,有时我们在维护他人代码或者使用一些模版库,碰到KL规则带来的副作用的时候,我们应该懂得如何去识别和解决它。

PS
示例代码在
VC 8.0 Gnu C++ 3.4.2 中编译验证通过。

  相关解决方案