两阶段功能模板编译:不是*仅* ADL 在第 2 阶段使用?

Two-phase function template compilation: not *only* ADL is employed in the 2nd phase?

我想知道为什么 following code 可以编译。

#include <iostream>

template<class T> 
void print(T t) {
    std::cout << t;
}

namespace ns {
    struct A {};
}

std::ostream& operator<<(std::ostream& out, ns::A) {
    return out << "hi!";
}

int main() {
    print(ns::A{}); 
}

我的印象是在实例化点通过 ADL 查找不合格的依赖名称 - 这不应该考虑全局命名空间。我错了吗?

这是一个有趣的案例。此处总结了您所描述的名称查找的工作原理:

[temp.dep.candidate] (emphasis mine)

1 For a function call where the postfix-expression is a dependent name, the candidate functions are found using the usual lookup rules ([basic.lookup.unqual], [basic.lookup.argdep]) except that:

  • For the part of the lookup using unqualified name lookup, only function declarations from the template definition context are found.

  • For the part of the lookup using associated namespaces ([basic.lookup.argdep]), only function declarations found in either the template definition context or the template instantiation context are found.

If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.

我强调的一点是问题的症结所在。 "ADL only" 的描述是针对来自 foo(bar) 的函数调用!它没有提到由重载运算符引起的调用。我们知道调用重载运算符等同于调用函数,但是该段说的是特定形式的表达式,只是函数调用。

如果将函数模板更改为

template<class T> 
void print(T t) {
    return operator<< (std::cout, t);
}

现在函数是通过后缀表达式表示法调用的,然后你看:GCC emits an equivalent error to Clang。它可靠地实现了上述段落,只是在涉及重载运算符调用时没有。

这是一个错误吗?我会说是的。这样做的目的肯定是像命名函数一样找到重载运算符(即使从它们各自的表达式形式调用时也是如此)。所以GCC需要修复。但该标准也可以对措辞进行细微的说明。