接受的答案是完全错误的——这不是 ADL 的错误。它展示了在日常编码中使用函数调用的粗心反模式——忽略依赖名称并盲目依赖不合格的函数名称。
简而言之,如果您在函数调用的postfix-expression 中使用非限定名称,您应该承认您已授予该函数可以在其他地方“覆盖”的能力(是的,这是一种静态多态性)。因此,C++ 中函数的非限定名称的拼写正是 interface 的一部分。
在接受答案的情况下,如果print_n 确实需要ADL print(即允许它被覆盖),则应该使用不合格的print 作为明确的通知来记录它,因此客户将收到一份合同,应谨慎声明print,不当行为将由my_stuff 承担全部责任。否则就是print_n的bug。修复很简单:使用前缀utility:: 限定print。这确实是print_n的bug,但几乎不是语言中ADL规则的bug。
但是,确实在语言规范中存在不需要的东西,而且从技术上讲,只有一个。它们实现了 10 多年,但语言中的任何内容都没有固定下来。接受的答案错过了它们(除了最后一段直到现在才完全正确)。有关详细信息,请参阅此paper。
我可以针对讨厌的名称查找附加一个真实案例。我正在实施is_nothrow_swappable,其中__cplusplus < 201703L。一旦我的命名空间中有一个声明的 swap 函数模板,我发现不可能依靠 ADL 来实现这样的功能。这样的swap 总是会与由惯用的using std::swap; 引入的std::swap 一起找到,以在ADL 规则下使用ADL,然后会出现swap 的歧义,其中swap 模板(将实例化is_nothrow_swappable得到正确的noexcept-specification) 被调用。结合两阶段查找规则,一旦包含 swap 模板的库头被包含在内,声明的顺序就不算在内。因此,除非我用专门的swap 函数重载我的库类型所有(以抑制任何候选泛型模板swap 在 ADL 之后通过重载解析匹配),否则我无法声明模板。具有讽刺意味的是,在我的命名空间中声明的 swap 模板正是为了利用 ADL(考虑 boost::swap),它是我的库中 is_nothrow_swappable 最重要的直接客户之一(顺便说一句,boost::swap 不尊重异常规格)。这完全打破了我的目的,叹息......
#include <type_traits>
#include <utility>
#include <memory>
#include <iterator>
namespace my
{
#define USE_MY_SWAP_TEMPLATE true
#define HEY_I_HAVE_SWAP_IN_MY_LIBRARY_EVERYWHERE false
namespace details
{
using ::std::swap;
template<typename T>
struct is_nothrow_swappable
: std::integral_constant<bool, noexcept(swap(::std::declval<T&>(), ::std::declval<T&>()))>
{};
} // namespace details
using details::is_nothrow_swappable;
#if USE_MY_SWAP_TEMPLATE
template<typename T>
void
swap(T& x, T& y) noexcept(is_nothrow_swappable<T>::value)
{
// XXX: Nasty but clever hack?
std::iter_swap(std::addressof(x), std::addressof(y));
}
#endif
class C
{};
// Why I declared 'swap' above if I can accept to declare 'swap' for EVERY type in my library?
#if !USE_MY_SWAP_TEMPLATE || HEY_I_HAVE_SWAP_IN_MY_LIBRARY_EVERYWHERE
void
swap(C&, C&) noexcept
{}
#endif
} // namespace my
int
main()
{
my::C a, b;
#if USE_MY_SWAP_TEMPLATE
my::swap(a, b); // Even no ADL here...
#else
using std::swap; // This merely works, but repeating this EVERYWHERE is not attractive at all... and error-prone.
swap(a, b); // ADL rocks?
#endif
}
尝试https://wandbox.org/permlink/4pcqdx0yYnhhrASi 并将USE_MY_SWAP_TEMPLATE 转换为true 以查看歧义。
2018 年 11 月 5 日更新:
啊哈,今天早上我又被 ADL 咬了。这次它甚至与函数调用无关!
今天我完成了将ISO C++17 std::polymorphic_allocator 移植到我的代码库的工作。由于我的代码中很久以前就引入了一些容器类模板(例如this),所以这次我只是将声明替换为别名模板,例如:
namespace pmr = ystdex::pmr;
template<typename _tKey, typename _tMapped, typename _fComp
= ystdex::less<_tKey>, class _tAlloc
= pmr::polymorphic_allocator<std::pair<const _tKey, _tMapped>>>
using multimap = std::multimap<_tKey, _tMapped, _fComp, _tAlloc>;
...所以它可以默认使用my implementation of polymorphic_allocator。 (免责声明:它有一些已知的错误。错误的修复将在几天内提交。)
但它突然不起作用,有数百行神秘的错误消息......
错误从this line 开始。它大致抱怨声明的BaseType 不是封闭类MessageQueue 的基础。这看起来很奇怪,因为别名是用与类定义的 base-specifier-list 中完全相同的标记声明的,而且我确信它们中的任何一个都不能被宏扩展。那为什么呢?
答案是……ADL 很烂。 The line inroducing BaseType 是使用 std 名称作为模板参数硬编码的,因此将根据 ADL 规则在类范围内查找模板。因此,它找到了std::multimap,这与在封闭命名空间范围中声明的实际基类中查找的结果不同。由于std::multimap 使用std::allocator 实例作为默认模板参数,BaseType 与具有polymorphic_allocator 实例的实际基类的类型不同,即使在封闭命名空间中声明的multimap 也被重定向到@ 987654375@。通过在= 中添加封闭条件作为前缀权限,修复了该错误。
我承认我很幸运。错误消息将问题指向这一行。只有 2 个类似的问题,the other 没有任何明确的 std(其中 string 是 my own one 适应 ISO C++17 的 string_view 更改,而不是 std 一个在 pre-C++ 17 种模式)。我不会这么快就发现这个错误是关于 ADL 的。