Why does unqualified name lookup during template instantiation not consider the name of the calling function? Specifically, why does the static_assert
fail in the following examples?
Example 1: Simple Non-Member Function
template <typename T>
inline constexpr bool has_foo = requires(T& t) { foo(t); };
void foo(int x) { static_assert(has_foo<int>); }
int main() { foo(42); }
Example 2: Non-Member Template Function
template <typename T>
inline constexpr bool has_foo = requires(T& t) { foo(t); };
void foo(auto x) { static_assert(has_foo<decltype(x)>); }
int main() { foo(42); }
In both cases, the assert fires.
In both cases, the static_assert fails. I have reviewed the following resources but have not found a clear explanation for this behavior:
Can someone explain why the name of the calling function is not considered during unqualified name lookup in these scenarios?
C++ has long had the rule that name lookup in templates is done from the point of definition, except that if the name is dependent then argument-dependent lookup is also done from the point of instantiation (and this part looks only in the associated namespaces, not in the scopes surrounding the definition). This can be viewed as a compromise between two objectives.
swap
, or especially if it uses operators that might be overloaded, you want the overload for the type that the template is instantiated with to be picked up, even though those types might not yet be defined at the point where the template is defined. An obvious example is that you get the definition of std::sort
when you include <algorithm>
(most likely somewhere near the top of your file) but you expect <
appearing in the definition of std::sort
to find the operator<
for your own type which you probably defined somewhere below.Since the introduction of modules in C++20 we no longer do the argument-dependent part of the lookup from the "point of instantiation" but rather the instantiation context, which is a set consisting of possibly multiple points. (Note that the instantiation context for an instantiated template specialization always includes at least the point of instantiation.)
In the current draft (at the time of writing of this answer), [basic.lookup.unqual]/4 is the rule that specifies that unqualified lookup finds only those declarations that are declared prior to the occurrence of the name being looked up:
An unqualified name is [...] Unless otherwise specified, such a name undergoes unqualified name lookup from the point where it appears.
And [basic.lookup.argdep]/4 is the rule that allows the argument-dependent portion of the lookup to find names that may be declared after the point where the name is used:
[...] If the lookup is for a dependent name ([temp.dep], [temp.dep.candidate]), the above lookup is also performed from each point in the instantiation context ([module.context]) of the lookup, additionally ignoring any declaration that appears in another translation unit, is attached to the global module, and is either discarded ([module.global.frag]) or has internal linkage.