由ADL引起的对模板化函数的模糊调用

时间:2010-09-09 10:47:10

标签: c++ visual-c++ templates argument-dependent-lookup

我几次被这个问题所困扰,所以我的同事也是如此。编译时

#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命名空间中定义的类型,触发编译器尝试找到boost::algorithm::find(RangeT& Input, const FinderT& Finder)的ADL。

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

4 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:3)

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

C ++ 03 3.4.2

  

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

     

[...]

     

- 如果T是类类型(包括联合),则其关联的类是:类本身;它所属的类   会员,如果有的话; 及其直接和间接基类。其关联的命名空间是命名空间   其中定义了相关的类。

     

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

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

答案 2 :(得分:3)

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

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

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

(find)( deq, STest2() );

答案 3 :(得分: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");
  }
}

此处(除非vectordeque包含允许的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
  • 选取
  • 那些不是,并且您应明确有资格避免此类问题

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