为什么两阶段查找无法选择"交换"的重载版本?
Why will two-phase lookup fail to choose overloaded version of 'swap'?
我正在研究有关最佳实践的最佳问题,以实现用户定义的类型的最佳实践。(我的问题最初是出于讨论在名称空间std
中添加类型的非法性的讨论。)
我不会在此处的上述答案中重新打印代码片段。
相反,我想了解答案。
我上面已链接的答案,在第一个代码段下方,涉及在namespace std
中的Overloading swap
(而不是在该名称空间中使用专业化):
如果您的编译器打印出不同的东西,那不是 正确实现模板的"两相查找"。
答案然后继续指出,在namespace std
中专门使用swap
(而不是 Overloading IT)产生不同的结果( perifeiratization )。
但是,答案以其他情况进行:专门为用户定义的 template class - - 在这种情况下,再次无法实现所需结果。
不幸的是,答案简单 state 事实;它不会解释为什么。
有人可以详细说明该答案,并在该答案中提供的两个特定代码段中描述查找过程:
-
namespace std
中的swap
过载CC_7用于用户定义的非网板类(如链接答案的第一个代码段) -
在
swap
0中专门针对用户定义的模板类(如链接答案的最终代码段)
在这两种情况下,都称为通用std::swap
,而不是用户定义的swap
。为什么?
(这将阐明两相查找的性质,以及实现用户定义的swap
的最佳实践的原因。)
preamble具有大量标准
示例中对swap()
的调用需要一个因名称,因为其参数begin[0]
和begin[1]
取决于周围algorithm()
功能模板的模板参数T
。标准在以下标准中定义了此类相关名称的两相名称查找:
14.6.4.2候选功能[temp.dep.candidate]
1的函数调用,其中帖子固定表达是一个因名称, 使用通常的查找规则(3.4.1, 3.4.2)除此之外:
- 在查找的部分中,使用未定期的名称查找(3.4.1),仅函数声明来自模板定义 找到上下文。
- 使用关联的查找部分 名称空间(3.4.2),仅在任何一个 模板定义上下文或模板实例化上下文是 找到。
不合格的查找由
定义3.4.1未定分名称查找[basic.lookup.unqual]
1在3.4.1中列出的所有情况下,将搜索范围 在每个类别中列出的顺序中声明; 名称查找后立即结束该名称。如果不 发现声明,该程序不正确。
和与参数有关的查找(ADL)作为
3.4.2参数依赖性名称查找[basic.lookup.argdep]
1当函数调用(5.2.2)中的后缀表表达为 an 不合格的ID ,在通常的情况下未考虑其他名称空间 可以搜索不合格的查找(3.4.1),在这些名称空间中, 命名空间范围的朋友函数或功能模板声明 (11.3)在其他情况下找不到。这些修改对 搜索取决于参数的类型(以及模板模板 参数,模板参数的名称空间)。
将标准应用于示例
第一个示例调用exp::swap()
。这不是因名称,也不需要两相名称查找。由于互换的调用是合格的,因此发生了普通查找,仅找到通用swap(T&, T&)
功能模板。
第二个示例(@howardhinnant称之为"现代解决方案")称swap()
为CC_21,并且在与class A
寿命的同一命名空间中也具有过载swap(A&, A&)
(在这种情况下为全局名称空间)。因为互换的呼吁是不合格的,所以普通查找和ADL都在定义点发生(再次找到通用的swap(T&, T&)
),但在实例化点发生了另一个ADL(即在main()
中调用exp::algorithm()
),这是这样拾取swap(A&, A&)
,这是在超载分辨率期间更好的匹配。
到目前为止一切都很好。现在为Encore:第三个示例调用swap()
,并且在namespace exp
中具有专业template<> swap(A&, A&)
。查找与第二个示例中的查找相同,但是现在ADL不拾取模板专业化,因为它不在class A
的关联名称空间中。但是,即使专业template<> swap(A&, A&)
在过载分辨率期间不起作用,但仍在使用点进行实例化。
最后,第四个示例调用swap()
,并且在namespace exp
内有一个过载template<class T> swap(A<T>&, A<T>&)
,用于template<class T> class A
居住在全局名称空间中。查找与第三个示例中的查找相同,并且ADL再次没有接收过载swap(A<T>&, A<T>&)
,因为它不在类模板A<T>
的关联名称空间中。在这种情况下,也没有必须在使用点进行实例化的专业化,因此在此处调用通用swap(T&, T&)
。
结论
即使您不允许您将新的过载添加到namespace std
,并且只有明确的专业化,它甚至由于两相名称查找的各种复杂性而无法工作。
对于用户定义的类型,namespace std
中的swap
不可能。简介namespace std
中的过载(与专业相反)是不确定的行为(在标准下是非法的,无需诊断)。
不可能将template
类的函数一般专业(而不是template
类实例 - IE,std::vector<int>
是一个实例,而std::vector<T>
是整个template
类)。似乎是专业化实际上是一个过载。因此,第一段适用。
实现用户定义的swap
的最佳实践是在与template
或class
相同的名称空间中引入swap
功能或过载。
然后,如果在正确的上下文(using std::swap; swap(a,b);
)中调用swap
,这就是std
库中的称呼,ADL将启动,并且您的过载将被发现。
另一个选项是针对您的特定类型进行std
中swap
的完整专业化。对于template
类是不可能的(或不切实际的),因为您需要专门针对存在的template
类的每个实例。对于其他类别,它是脆弱的,因为专业化仅适用于该特定类型:子类也必须在std
中进行重新定位。
通常,功能的专业化非常脆弱,最好引入超r效力。由于您无法将覆盖物引入std
,因此它们将在您自己的namespace
中可靠地发现的唯一位置。在您自己的命名空间中,此类替代也优于std
中的覆盖。
有两种方法可以将swap
注入您的命名空间。两者都是为此目的:
namespace test {
struct A {};
struct B {};
void swap(A&, A&) { std::cout << "swap(A&,A&)n"; }
struct C {
friend void swap(C&, C&) { std::cout << "swap(C&, C&)n"; }
};
void bob() {
using std::swap;
test::A a, b;
swap(a,b);
test::B x, y;
swap(x, y);
C u, v;
swap(u, v);
}
}
void foo() {
using std::swap;
test::A a, b;
swap(a,b);
test::B x, y;
swap(x, y);
test::C u, v;
swap(u, v);
test::bob();
}
int main() {
foo();
return 0;
}
第一个是将其直接注入namespace
,第二个是将其包含在Inline friend
中。"外部运算符"的直列friend
是一种常见模式,基本上意味着您只能通过ADL找到swap
,但是在此特定上下文中,它不会添加太多。
- 如何使用默认参数等选择模板专业化
- 如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
- 选择要调用的构造函数
- C++选择排序算法中的逻辑错误
- QTreeView幻灯片多选后无法使用单击选择
- 无法获取菜单选择以运行函数.C++
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- C++嵌套if语句,基本货币交换
- 在C++中,如何通过几种类型从元组中选择多个元素
- shell排序中的交换和比较
- 讨论 - 创建矩阵时的数组与向量的向量 - 什么是最实用的选择
- 对可变参数使用声明.如何选择正确的功能
- 选择选举获胜者的程序
- 排序时无法执行交换操作.我做的时候它会崩溃.为什么
- 选择排序时交换函数调用的数量和完成的交换次数是否相同?
- 修改的选择排序,选择最大的数字,然后交换到最后
- 从递归向后选择排序函数调用 max 和交换函数
- 跟踪选择排序中的交换和比较次数
- 为什么两阶段查找无法选择"交换"的重载版本?
- 为什么 a = (a+b) - (b=a) 是交换两个整数的糟糕选择