由于 ADL 对模板化函数的调用不明确

发布于 2024-09-18 13:17:14 字数 1057 浏览 12 评论 0原文

我和我的同事都被这个问题困扰过几次。编译时

#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(…) 的调用前添加“::”,但为什么这是必要的呢?全局命名空间中存在完全有效的匹配,那么为什么要调用参数依赖查找呢?有人可以解释一下这里的原理吗?

I've been bitten by this problem a couple of times and so have my colleagues. When compiling

#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
}

...the VS9 compiler fails when compiling the second find. This is due to the fact that STest2 inherits from a type that is defined in boost namespace which triggers the compiler to try ADL which finds boost::algorithm::find(RangeT& Input, const FinderT& Finder).

An obvious solution is to prefix the call to find(…) with "::" but why is this necessary? There is a perfectly valid match in the global namespace, so why invoke Argument-Dependent Lookup? Can anybody explain the rationale here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

故事未完 2024-09-25 13:17:14

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

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

ADL isn't a fallback mechanism to use when "normal" overload resolution fails, functions found by ADL are just as viable as functions found by normal lookup.

If ADL was a fallback solution then you might easily fall into the trap were a function was used even when there was another function that was a better match but only visible via ADL. This would seem especially strange in the case of (for example) operator overloads. You wouldn't want two objects to be compared via an operator== for types that they could be implicitly converted to when there exists a perfectly good operator== in the appropriate namespace.

桃扇骨 2024-09-25 13:17:14

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

C++03 3.4.2

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

[...]

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

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

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

I'll add the obvious answer myself because I just did some research on this problem:

C++03 3.4.2

§2 For each argument type T in the function call, there is a set of zero or more associated namespaces [...] The sets of namespaces and classes are determined in the following way:

[...]

— If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a
member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces
in which its associated classes are defined.

§ 2a If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated
namespaces and classes are not considered. Otherwise the set of declarations found by the lookup of
the function name is the union of the set of declarations found using ordinary unqualified lookup and the set
of declarations found in the namespaces and classes associated with the argument types.

At least it's standard conformant, but I still don't understand the rationale here.

瞎闹 2024-09-25 13:17:14

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

我认为这也遵循替换原则 - 类名称空间中的函数被视为其接口的一部分(请参阅 Herb Sutter 的“类中有什么?”)。因此,适用于基类的接口应该继续适用于派生类。

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

(find)( deq, STest2() );

Consider a mystream which inherits from std::ostream. You would like that your type would support all the << operators that are defined for std::ostream normally in the std namespace. So base classes are associated classes for ADL.

I think this also follows from the substitution principle - and functions in a class' namespace are considered part of its interface (see Herb Sutter's "What's in a class?"). So an interface that works on the base class should remain working on a derived class.

You can also work around this by disabling ADL:

(find)( deq, STest2() );
神经大条 2024-09-25 13:17:14

我认为你自己已经说明了问题:

在全局命名空间

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

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);

当然,重载解析将失败,表明没有匹配。

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

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

  • 那些是类接口的一部分,在同一命名空间中声明,由 ADL 选取
  • 那些不是,您应该显式限定避免此类问题

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

I think you stated the problem yourself:

in the global namespace

Functions in the global namespace are considered last. It's the most outer scope by definition. Any function with the same name (not necessarily applicable) that is found in a closer scope (from the call point of view) will be picked up first.

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");
  }
}

Here (unless vector or deque include algorithm, which is allowed), the only method that will be picked up during name look-up will be:

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

If algorithm is somehow included, there will also be:

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

And of course, overload resolution will fail stating that there is no match.

Note that name-lookup is extremely dumb: neither arity nor argument type are considered!

Therefore, there are only two kinds of free-functions that you should use in C++:

  • Those which are part of the interface of a class, declared in the same namespace, picked up by ADL
  • Those which are not, and that you should explicitly qualified to avoid issues of this type

If you fall out of these rules, it might work, or not, depending on what's included, and that's very awkward.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文