7

我已经被这个问题咬过几次了,我的同事也是如此。编译时

#include <deque>
#include <boost/algorithm/string/find.hpp>
#include <boost/operators.hpp>

template< class Rng, class T >    
typename boost::range_iterator<Rng>::type find( Rng& rng, T const& t ) {
      return std::find( boost::begin(rng), boost::end(rng), t );
}

struct STest {
      bool operator==(STest const& test) const { return true; }
};

struct STest2 : boost::equality_comparable<STest2>   {
      bool operator==(STest2 const& test) const { return true; }
};

void main() {
      std::deque<STest> deq;
      find( deq, STest() ); // works
      find( deq, STest2() ); // C2668: 'find' : ambiguous call to overloaded function
}

...编译第二个查找时 VS9 编译器失败。这是因为STest2继承自 boost 命名空间中定义的类型,这会触发编译器尝试 ADL,从而找到boost::algorithm::find(RangeT& Input, const FinderT& Finder).

一个明显的解决方案是在调用前find(…)加上“ ::”,但为什么这是必要的?全局命名空间中有一个完全有效的匹配,那么为什么要调用 Argument-Dependent Lookup 呢?有人可以在这里解释理由吗?

4

4 回答 4

7

当“正常”重载解析失败时,ADL 不是备用机制,ADL 找到的函数与正常查找找到的函数一样可行。

如果 ADL 是一种后备解决方案,那么即使使用了一个函数,即使有另一个更好匹配但只能通过 ADL 看到的函数,您也可能很容易陷入陷阱。在(例如)运算符重载的情况下,这似乎特别奇怪。您不希望通过 for 类型比较两个对象,当在适当的命名空间中operator==存在完美时,它们可以被隐式转换为。operator==

于 2010-09-09T10:58:38.113 回答
3

我将自己添加明显的答案,因为我刚刚对这个问题进行了一些研究:

C++03 3.4.2

§2 对于函数调用中的每个参数类型 T,有一组零个或多个关联的命名空间 [...] 命名空间和类的集合按以下方式确定:

[...]

— 如果 T 是类类型(包括联合),则其关联的类是:类本身;它所属的类别(如有的话);及其直接和间接基类。其关联的名称空间是定义其关联类的名称空间。

§ 2a如果名称的普通非限定查找找到类成员函数的声明,则不考虑相关的命名空间和类。否则,通过查找函数名找到的声明集是使用普通非限定查找找到的声明集与在与参数类型关联的命名空间和类中找到的声明集的并集。

至少它符合标准,但我仍然不明白这里的基本原理。

于 2010-09-09T10:52:23.953 回答
3

考虑mystream继承自的 a std::ostream。您希望您的类型支持通常在 std 命名空间<<中定义的所有运算符。std::ostream所以基类是 ADL 的关联类。

我认为这也遵循替换原则——类名称空间中的函数被认为是其接口的一部分(参见 Herb Sutter 的“类中有什么?”)。因此,在基类上工作的接口应该在派生类上继续工作。

您也可以通过禁用 ADL 来解决此问题:

(find)( deq, STest2() );
于 2010-09-09T10:56:33.530 回答
1

我想你自己陈述了这个问题:

在全局命名空间中

全局命名空间中的函数被认为是最后的。根据定义,它是最外层的范围。在更近的范围(从调用的角度)中找到的任何具有相同名称(不一定适用)的函数将首先被拾取。

template <typename Rng, typename T>
typename Rng::iterator find( Rng& rng, T const& t );

namespace foo
{
  bool find(std::vector<int> const& v, int);

  void method()
  {
    std::deque<std::string> deque;
    auto it = find(deque, "bar");
  }
}

在这里(除非vectordequeinclude algorithm,这是允许的),在名称查找期间将选择的唯一方法是:

bool foo::find(std::vector<int> const&, int);

如果algorithm以某种方式包括在内,还会有:

template <typename FwdIt>
FwdIt std::find(FwdIt begin, FwdIt end,
                typename std::iterator_traits<FwdIt>::value_type const& value);

当然,重载决议将失败,说明没有匹配。

请注意,名称查找非常愚蠢:既不考虑arity 也不考虑参数类型!

因此,在 C++ 中应该使用的自由函数只有两种:

  • 那些属于类接口的一部分,在同一个命名空间中声明,由 ADL 拾取
  • 那些不是,并且您应该明确限定以避免此类问题

如果你不遵守这些规则,它可能会起作用,也可能不会,这取决于所包含的内容,这非常尴尬。

于 2010-09-09T12:03:33.450 回答