为什么 ADL 优先于'std namespace'中的函数,但等于用户定义命名空间中的函数?

Why does ADL take precedence over a function in 'std namespace' but is equal to function in user-defined namespace?

本文关键字:函数 用户 命名空间 定义 于用户 namespace std 优先于 ADL 为什么      更新时间:2023-10-16

我有两个ADL片段用于演示。这两个代码段都是由VC10、gcc&comeau C++编译器,并且这三个编译器的结果都是相同的。

<1> ADL反对使用用户定义命名空间的指令:

#include <algorithm>
namespace N 
{ 
struct T {}; 
void swap(T,T) {} 
} 
namespace M 
{ 
void swap(N::T,N::T) {} 
} 
int main() 
{ 
using M::swap; 
N::T o1,o2;
swap(o1,o2); 
}

编译结果:

error C2668: 'M::swap' : ambiguous call to overloaded function
could be 'void M::swap(N::T,N::T)'
or       'void N::swap(N::T,N::T)' [found using argument-dependent lookup]

这是意料之中的,因为ADL不优先于正常查找结果,加上ADL不是二等公民,ADL搜索结果与正常(非ADL)无故障查找不一致。这就是为什么我们有歧义。

<2> ADL反对使用std命名空间的指令:

#include <algorithm>
namespace N 
{ 
struct T {}; 
void swap(T,T) {}  //point 1
} 
namespace M 
{ 
void swap(N::T,N::T) {} 
} 
int main() 
{ 
using std::swap; 
N::T o1,o2;
swap(o1,o2); 
}

这个编译正常。

结果是编译器选择ADL结果(它以std::swap为先例),这意味着"点1"处的N::swap()将被调用。只有在没有"点1"的情况下(比如说,如果我注释掉那一行),编译才会使用回退std::swap

注意,这种方式已经在许多地方被用作覆盖std::swap的方式。但我的问题是,为什么ADL优先于"std命名空间"(case2),但被认为等于用户定义的命名空间函数(case1)?

C++标准中有没有这样的段落?

=================================================================================阅读有用的答案后进行编辑,可能对其他人有所帮助。

所以我调整了我的片段1&现在模糊性消失了,编译时显然更喜欢Nontemplate函数进行重载解析!

#include <algorithm>
namespace N 
{ 
struct T {}; 
void swap(T,T) {} 
} 
namespace M 
{ 
template<class T>
void swap(N::T,N::T) {} 
} 
int main() 
{ 
using M::swap;
N::T o1,o2;
swap(o1,o2); //here compiler choose N::swap()
}

我还调整了我的片段2。只是为了让歧义看起来只是为了好玩!

#include <algorithm>
namespace N 
{ 
struct T {}; 
template<class _Ty> inline
void swap(_Ty& _Left, _Ty& _Right)
{
_Ty _Tmp = _Move(_Left);
_Left = _Move(_Right);
_Right = _Move(_Tmp);
}
} 
namespace M 
{ 
void swap(N::T,N::T) {} 
} 
int main() 
{ 
using std::swap; 
N::T o1,o2;
swap(o1,o2); 
}

gcc和comeau都表示歧义如预期:

"std::swap" matches the argument list, the choices that match are:
function template "void N::swap(_Ty &, _Ty &)"
function template "void std::swap(_Tp &, _Tp &)"

BTW VC10像往常一样愚蠢,除非我删除"使用std::swap",否则让这一次通过。

还有一点要写:C++重载可能很棘手(C++标准中有30多页),但在appendlix B中有一个可读性很强的10页。。。

感谢所有的良好投入,现在一切都清楚了。

函数调用分几个阶段进行:

  1. 名称查找->将候选函数放入所谓的重载集
    • ADL的部分,如果您有一个不合格的名称查找
  2. 模板参数推导->用于重载集中的每个模板
  3. 过载分辨率->选择最佳匹配

您混淆了第1部分和第3部分。名称查找实际上会将两个swap函数都放在重载集({N::swap, std::swap})中,但第3部分将决定最终调用哪一个。

现在,由于std::swap是一个模板,并且标准规定在进行过载解析时,非模板函数比模板函数更专业,因此<2>调用N::swap:

§13.3.3 [over.match.best] p1

给定这些定义,如果〔…〕,则可行函数F1被定义为比另一个可行函数F2更好的函数

  • F1是一个非模板函数,F2是一个函数模板专门化〔…〕

†我推荐这一优秀系列的前三个视频。

您的测试不检查ADL是否优先于通常的查找,而是检查过载分辨率如何确定最佳匹配。第二个测试用例之所以有效,是因为std::swap是一个模板,当对(ADL找到的)完美匹配和模板执行重载解析时,非模板化函数优先。

相关文章: