为什么 ADL 无法使用 std::get 解析为正确的函数

Why ADL does not resolve to the correct function with std::get

我正在尝试编写一个模板函数,该函数使用已解析的 ADL get 来获取 struct/range(tuple-esque)的成员。

#include <iostream>
#include <utility>
#include <tuple>

int main() {
    auto tup = std::make_tuple(1, 2);
    std::cout << get<0>(tup) << std::endl;
}

我这样做是因为结构化绑定提案 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf §11.5.3) 说明了如何使用 get 从结构中获取元素。它说非成员 get 用于从结构中获取元素。

我假设上面的代码可以编译,因为 ADL 会导致在 std 命名空间中查找 get 函数(因为它的参数是 std::tuple<int, int> 类型,在 std) 中可以找到它。但是,我得到一个错误。有人可以在这里解释正确的方法以及为什么上面的代码不起作用吗?在这种情况下如何强制 ADL 发生?

参数相关查找的工作方式不同 for function templates where an explicit template argument is given

Although a function call can be resolved through ADL even if ordinary lookup finds nothing, a function call to a function template with explicitly-specified template arguments requires that there is a declaration of the template found by ordinary lookup (otherwise, it is a syntax error to encounter an unknown name followed by a less-than character)

基本上,非限定查找需要某种方式来找到模板函数。然后,ADL 就可以启动了(因为名字 get 被认为是一个模板)。 cppreference举个例子:

namespace N1 {
  struct S {};
  template<int X> void f(S);
}
namespace N2 {
  template<class T> void f(T t);
}
void g(N1::S s) {
  f<3>(s);      // Syntax error (unqualified lookup finds no f)
  N1::f<3>(s);  // OK, qualified lookup finds the template 'f'
  N2::f<3>(s);  // Error: N2::f does not take a non-type parameter
                //        N1::f is not looked up because ADL only works
                //              with unqualified names
  using N2::f;
  f<3>(s); // OK: Unqualified lookup now finds N2::f
           //     then ADL kicks in because this name is unqualified
           //     and finds N1::f
}

结构化绑定是一种特殊情况,启用了 ADL。

In the following contexts ADL-only lookup (that is, lookup in associated namespaces only) takes place:

  • the lookup of non-member functions begin and end performed by the range-for loop if member lookup fails
  • the dependent name lookup from the point of template instantiation.
  • the lookup of non-member function get performed by structured binding declaration for tuple-like types

已强调

问题最终是模板:

std::cout << get<0>(tup) << std::endl;
//           ~~~~

此时,编译器还不知道这是一个需要使用 ADL 查找的函数 - get 只是一个名称。由于该名称本身找不到任何内容,因此这将被解释为未知名称后跟小于号。要让它工作,你需要一些其他函数模板 get visible:

using std::get;
std::cout << get<0>(tup) << std::endl; // now, OK

即使它什么都不做:

template <class T> void get();

int main() {
    auto tup = std::make_tuple(1, 2); 
    std::cout << get<0>(tup) << std::endl;
}

结构化绑定措辞使用依赖于参数的查找显式查找 get,因此它避免了从 [dcl.struct.bind 中使用名为 get 的已经可见的函数模板的需要]:

The unqualified-id get is looked up in the scope of E by class member access lookup, and if that finds at least one declaration, the initializer is e.get<i>(). Otherwise, the initializer is get<i>(e), where get is looked up in the associated namespaces. In either case, get<i> is interpreted as a template-id. [ Note: Ordinary unqualified lookup is not performed. — end note ]

笔记是关键。如果我们执行了不合格的查找,我们就会失败。

快进到 C++20

p0846r0 已被 C++20 接受,现在允许 ADL 使用显式模板参数调用模板函数。

所以 OP 的代码 now compiles as is with C++20 没有错误!