Should this function call be ambiguous?
Asked Answered
U

2

6

I stumbled on this the other day and can't figure out which answer is correct, or whether both are acceptable.

Specifically, I'm referring to the call to bar(T{}) in OtherFunction. From what I've been able to test on compiler explorer, the decision seems split. msvc and icc agree that it is ambiguous while gcc and clang compile the code without issue.

The function bar inside the namespace hidden becomes visible through argument-dependent lookup. Additionally, msvc/icc consider the declaration of bar in the global namespace as a candidate while gcc/clang do not. It seems that the declaration in the global namespace should not be considered since it is declared after the call to bar(T{}) but I'm not sure whether I'm reading the rules for unqualified name lookup correctly or if the standard is ambiguous in this regard.

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

EDIT: Looks like msvc has fixed this as long as /permissive- option is used (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>();
}
Uncinate answered 8/6, 2019 at 1:3 Comment(0)
R
3

Gcc and Clang are correct. The global bar defined after the definition of OtherFunction can't be found by name lookup; while hidden::bar could be found by ADL.

(emphasis mine)

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

Rumen answered 8/6, 2019 at 1:31 Comment(0)
T
2

The code is valid, so msvc and icc are incorrect.

Since an argument of bar is type-dependent, the name bar is a dependent name, and is looked up only when the template OtherFunction is instantiated, not when the template is defined.

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.

So jumping to [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.

[The current C++20 draft has rearranged wordings in these sections. In particular, the rule about including the instantiation context for lookup of a dependent name in associated namespaces is now listed in [basic.lookup.argdep]/4.5, and is just a Note in [temp.dep.candidate]. I'm not sure if the reason for this is just for clarity, or might have something to do with effects of modules.]

X is the result of unqualified lookup for the name bar considering only declarations visible from the template definition context. But since the template definition context is the very beginning of your translation unit, obviously X is empty.

Since X doesn't contain anything at all, it doesn't contain the listed items which would force Y to be empty. So to determine Y, we look in the namespaces associated with the argument types. The argument type in this instantiation is hidden::Foo, so the only associated namespace is hidden, and the single result of name lookup is function hidden::bar.

::bar is not visible in this name lookup, so the bar(T{}) expression cannot be ambiguous.

Tyika answered 8/6, 2019 at 1:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.