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

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

提问人:RoundPi 提问时间:10/5/2012 最后编辑:RoundPi 更新时间:10/6/2012 访问量:1486

问:

我有两个用于演示目的的 ADL 片段。这两个片段都是由VC10,gcc和comeau C++编译器编译的,这三个结果都是相同的。

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

#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 命名空间的 using 指令:

#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”被调用。只有当没有“第 1 点”时(比如说,如果我注释掉那一行),编译才会使用回退。N::swap()std::swap

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

C++标准中有一段是这样说的吗?

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

所以我调整了我的片段 1,现在歧义消失了,在进行重载解析时编译更喜欢非模板函数!

#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 &)"

顺便说一句,VC10 像往常一样愚蠢,除非我删除“使用 std::swap”,否则让这个通过。

再写一点:C++重载可能很棘手(C++标准中有30+页),但是在附录B中有一个非常可读的10页......

感谢所有好的输入,现在很清楚了。

C++ 命名空间 using-directives argument-dependent-lookup

评论


答:

11赞 Xeo 10/5/2012 #1

函数调用分几个阶段进行

  1. 名称查找 -> 将候选函数放在所谓的重载集中
    • 这是 ADL 发生的部分,如果您有一个非限定的名称查找
  2. 模板参数推导 - 重载集中每个模板的 >
  3. 过载分辨率 ->选择最佳匹配

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

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

§13.3.3 [over.match.best] p1

鉴于这些定义,如果 [...]F1F2

  • F1是一个非模板函数,是一个函数模板专业化 [...]F2

† 我推荐这个关于该主题的优秀系列的前三个视频。

评论

0赞 RoundPi 10/5/2012
感谢您的标准参考!
0赞 Matthieu M. 10/5/2012
@Xeo:不知道(关于反对票),第一句话让我有点困惑,因为编译器没有找到所有可能被调用的函数。名称查找就是只找到一个子集。
0赞 Xeo 10/6/2012
@Matthieu:我有点过分概括了。修复和改进。
10赞 David Rodríguez - dribeas 10/5/2012 #2

测试不会检查 ADL 是否优先于常规查找,而是检查重载分辨率如何确定最佳匹配。第二个测试用例起作用的原因是它是一个模板,当对完美匹配(由 ADL 找到)和模板执行重载解决时,非模板化函数优先。std::swap

评论

0赞 Kerrek SB 10/5/2012
值得注意的一点是,ADL 与名称查找有关,而名称查找没有“优先级”的概念。
0赞 RoundPi 10/5/2012
@KerrekSB:我认为 David 说的是在超载分辨率中查找名称后阶段,这完全是关于选择最佳匹配。