围绕功能调用解决方案的混乱

时间:2015-05-04 14:12:51

标签: c++ language-lawyer function-calls overload-resolution argument-dependent-lookup

这个问题的灵感来自this one。考虑一下代码:

Class[] c = new Class[1];
c[0] = List.class;
XposedHelpers.callStaticMethod(XposedHelpers.findClass("com.android.internal.widget.LockPatternUtils", lpparam.classLoader), "patternToString", c, pattern);

经过GCC的一些测试后,我发现namespace ns { template <typename T> void swap(T& a, T& b) { using namespace std; swap(a, b); } } 解析为了 1)swap(a, b);如果std::swap超载T(例如,标准容器类型)
2)std::swap否则,导致无限递归 因此,似乎编译器将首先尝试在命名空间ns::swap中找到匹配项。如果找到匹配项,则搜索结束。但是当ADL进入时并非如此,在这种情况下,无论如何都会找到ns。解决过程似乎很复杂。

我想知道在上面的上下文中解析函数调用std::swap的过程中发生了什么的细节。可以参考该标准。

2 个答案:

答案 0 :(得分:6)

OP中的代码与等效

using std::swap; // only for name lookup inside ns::swap

namespace ns {
  template <typename T>
  void swap(T& a, T& b) {
    swap(a, b);
  }
}

为什么呢?因为像using namespace std;这样的 using-directives 具有非常特殊的行为C ++ 14 [namespace.udir] p2:

  

using-directive 指定指定命名空间中的名称   可以在 using-directive 之后出现的范围内使用    using-directive 。在非限定名称查找期间,名称   看起来好像是在最近的封闭命名空间中声明的   它包含 using-directive 和指定的命名空间。

包含名称空间std和函数ns::swap的块作用域的最近的封闭名称空间是全局名称空间。

另一方面,诸如using std::swap;之类的使用声明确实将名称引入它们出现的范围,而不是在某些封闭范围内。

查找函数调用表达式(如swap(a, b))称为非限定查找。标识符swap尚未使用任何名称空间或类名限定,而ns::swap则已通过ns::限定。对函数的潜在名称进行非限定查找包括两部分:纯粹的非限定查找和依赖于参数的查找。

纯不合格的查找将在包含该名称的最近的封闭范围处停止。在OP的示例中,如上面显示的等效转换所示,包含名称swap的声明的最近范围是命名空间ns。不会搜索全局范围,不会通过纯粹的非限定查找找到std::swap

依赖于参数的查找搜索与参数类型关联的所有范围(此处:仅命名空间和类)。对于类类型,声明类的名称空间是关联的作用域。 C ++标准库的类型(例如std::vector<int>)与命名空间std相关联,因此std::swap可以通过依赖于参数的查找找到swap(a, b) T swap是C ++标准库类型。类似地,您自己的类类型允许在声明它们的名称空间中找到namespace N2 { class MyClass {}; void swap(MyClass&, MyClass&); } 函数:

ns::swap

因此,如果依赖于参数的查找找不到比纯粹的非限定查找更好的匹配,那么你最终会递归调用swap

调用swap(a, b)不合格,即std::swap(a, b)而不是std::swap背后的想法是,通过参数依赖查找找到的函数被假定为比std::swap更专业化。专门为您自己的类模板类型设置std等函数模板是不可能的(因为禁止部分函数模板特化),并且您可能不会向命名空间std::swap添加自定义重载。 template<typename T> void swap(T& a, T& b) { T tmp( move(a) ); a = move(b); b = move(tmp); } 的通用版本通常如下实现:

var formatMoney = function (value) {
    // Convert the value to a floating point number in case it arrives as a string.
    var numeric = parseFloat(value);
    // Specify the local currency.
    return numeric.toLocaleString('USD', { style: 'currency', currency: "USD", minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

这需要一个移动构造加上两个移动分配,甚至可能会回复到副本。因此,您可以在与这些类​​型关联的名称空间中为您自己的类型提供专用交换函数。您的专业版可以使用您自己的类型的某些属性或私有访问权。

答案 1 :(得分:2)

标准中最重要的部分是7.3.4 / 2(引用C ++ 14 n4140,强调我的):

  

using-directive 指定指定命名空间中的名称可以在范围内使用    using-directive 出现在 using-directive之后。 在非限定名称查找(3.4.1)期间,名称出现   好像它们是在最近的封闭命名空间中声明的,它包含 using-directive 和   提名命名空间。

using-directive 位于:: ns的函数内,并指定:: std。这意味着,出于非限定名称查找的目的,此 using-directive 的效果是::std中的名称就像在::中声明它们一样。特别是,就像它们在::ns中一样。

因为非限定名称查找在::ns中的函数内开始,所以它会在查看::ns之前搜索::。它会找到::ns::swap,因此它会在不检查::的情况下结束,它会在 using-directive中找到::std::swap