为什么 ADL 优先于'std namespace'中的函数,但等于用户定义命名空间中的函数?
Why does ADL take precedence over a function in 'std namespace' but is equal to function in user-defined namespace?
我有两个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页。。。
感谢所有的良好投入,现在一切都清楚了。
函数调用分几个阶段进行†:
- 名称查找->将候选函数放入所谓的重载集
- ADL的部分,如果您有一个不合格的名称查找
- 模板参数推导->用于重载集中的每个模板
- 过载分辨率->选择最佳匹配
您混淆了第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找到的)完美匹配和模板执行重载解析时,非模板化函数优先。
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 使用用户定义函数的字符串反转
- 用户定义函数中的指针和输入
- 函数计算用户按下按钮的频率
- 函数如何通知用户它基于函数原型抛出异常?
- 参数包构造函数在类模板中隐藏用户定义的转换
- C++:用户定义的显式类型转换函数错误
- 是否有 Windows 用户空间函数来枚举连接的网络共享?
- 为用户定义的类正确调用复制构造函数/赋值运算符
- 为什么用户定义的函数不按照给定的顺序对相同长度的元素进行排序?
- 使用宏编译时使用用户定义的数学函数,或者仅使用 c++ 中标准数学库中的函数
- 在 C++ 中声明 const 对象需要用户定义的默认构造函数.如果我有一个可变成员变量,为什么不呢?
- 如何使用用户定义的函数覆盖 C lib 函数,如 _sbrk?
- C++ - 将结构类型的数组传递给用户定义的函数
- 修改链表主函数代码,用户将在其中输入节点的索引和数据以及正确的消息
- 使用用户定义的类型 UDT 实例化 std::atomic<>。如果 UDT 具有虚函数,则 l 墨水将失败。为什么?
- CURLOPT_INTERLEAVEFUNCTION回调函数始终接收 nullptr 作为用户数据指针
- 错误:调用'strcmp'没有匹配函数(尝试设置显示用户信息功能)
- 如何防止用户指定函数模板参数,迫使其进行推导
- 利用poll()函数(用户空间)的其他方法