这个函数调用应该是模棱两可的吗?

Should this function call be ambiguous?

前几天我无意中发现了这个问题,不知道哪个答案是正确的,或者两者是否都可以接受。

具体来说,我指的是在 OtherFunction 中对 bar(T{}) 的调用。从我能够在编译器资源管理器上进行的测试来看,这个决定似乎是分裂的。 msvc 和 icc 同意它是模棱两可的,而 gcc 和 clang 编译代码没有问题。

隐藏的命名空间内的功能栏通过参数相关查找变得可见。此外,msvc/icc 将全局命名空间中的 bar 声明视为候选,而 gcc/clang 则不考虑。似乎不应该考虑全局命名空间中的声明,因为它是在调用 bar(T{}) 之后声明的,但我不确定我是否正确阅读了不合格名称查找的规则,或者标准是否是在这方面模棱两可。

https://godbolt.org/z/HAS-Cv

编辑: 看起来 msvc 已经修复了这个问题,只要使用 /permissive- 选项 (https://devblogs.microsoft.com/cppblog/two-phase-name-lookup-support-comes-to-msvc/)

template <typename T>
inline void OtherFunction () {
    bar(T{});
}

namespace hidden {
    struct Foo {};
    inline void bar (Foo foo) {}
}

inline void bar (hidden::Foo foo) {}

void Function () {
    OtherFunction<hidden::Foo>();
}

Gcc 和 Clang 是正确的。 OtherFunction定义后定义的全局bar无法被name lookup; while hidden::bar could be found by ADL找到。

(强调我的)

For a dependent name used in a template definition, the lookup is postponed until the template arguments are known, at which time ADL examines function declarations with external linkage (until C++11) that are visible from the template definition context as well as in the template instantiation context, while non-ADL lookup only examines function declarations with external linkage (until C++11) that are visible from the template definition context (in other words, adding a new function declaration after template definition does not make it visible except via ADL).

代码有效,所以msvc和icc不正确。

由于bar的一个参数是type-dependent,名称bar是一个从属名称,只有在实例化模板OtherFunction时才会查找,而不是定义模板时。

C++17 [temp.dep.candidate]/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 ([basic.lookup.unqual]), 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 from either the template definition context or the template instantiation context are found.

所以跳转到 [basic.lookup.argdep]/3:

Let X be the lookup set produced by unqualified lookup ([basic.lookup.unqual]) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains

  • a declaration of a class member, or
  • a block-scope function declaration that is not a using-declaration, or
  • a declaration that is neither a function nor a function template

then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y.

[当前的 C++20 草案重新安排了这些部分的措辞。特别是,关于在关联命名空间中查找依赖名称的实例化上下文的规则现在列在 [basic.lookup.argdep]/4.5, and is just a Note in [temp.dep.candidate] 中。我不确定这样做的原因是否只是为了清楚起见,或者可能与模块的影响有关。]

X 是名称 bar 的非限定查找结果,仅考虑从模板定义上下文可见的声明。但是由于模板定义上下文是翻译单元的最开始,显然 X 是空的。

由于 X 根本不包含任何内容,因此它不包含会强制 Y 为空的列出项.因此,为了确定 Y,我们查看与参数类型关联的命名空间。此实例化中的参数类型是 hidden::Foo,因此唯一关联的命名空间是 hidden,名称查找的单个结果是函数 hidden::bar.

::bar 在此名称查找中不可见,因此 bar(T{}) 表达式不能有歧义。