This question is inspired by this one. Consider the code:
namespace ns {
template <typename T>
void swap(T& a, T& b) {
using namespace std;
swap(a, b);
}
}
After some test with GCC, I found that swap(a, b);
resolves to
1) std::swap
if T
has overloaded std::swap
(e.g., standard container types)
2) ns::swap
otherwise, leading to infinite recursion.
So, it seems that the compiler will first try to find a match in the namespace ns
. If a match is found, the search ends. But this is not the case when ADL comes in, in which case, std::swap
is found anyway. The resolution process seems to be complicated.
I want to know the details of what is going on under the hood in the process of resolving the function call swap(a, b)
in the above context. Reference to the standard would be appreciated.
The code in the OP is equivalent to this:
using std::swap; // only for name lookup inside ns::swap
namespace ns {
template <typename T>
void swap(T& a, T& b) {
swap(a, b);
}
}
Why? Because using-directives like using namespace std;
have a very peculiar behaviour C++14 [namespace.udir]p2:
A using-directive specifies that the names in the nominated namespace
can be used in the scope in which the using-directive appears after
the using-directive. During unqualified name lookup, the names
appear as if they were declared in the nearest enclosing namespace
which contains both the using-directive and the nominated namespace.
The nearest enclosing namespace that contains both namespace std
and the block scope of function ns::swap
is the global namespace.
Using-declarations such as using std::swap;
on the other hand really introduce names into the scope in which they appear, not in some enclosing scope.
The lookup of a function call expression such as swap(a, b)
is called unqualified lookup. The identifier swap
has not been qualified with any namespace or class name, as opposed to ns::swap
, which has been qualified via ns::
. Unqualified lookup for potential names of functions consists of two parts: pure unqualified lookup and argument-dependent lookup.
Pure unqualified lookup stops at the nearest enclosing scope that contains the name. In the OP's example, as illustrated by the equivalent transformation shown above, the nearest scope that contains a declaration of the name swap
is the namespace ns
. The global scope will not be searched, std::swap
will not be found via pure unqualified lookup.
Argument-dependent lookup searches all scopes (here: only namespaces and classes) associated with the argument types. For class types, the namespace in which the class has been declared in is an associated scope. Types of the C++ Standard Library such as std::vector<int>
are associated with namespace std
, hence std::swap
can be found via argument-dependent lookup for the expression swap(a, b)
if T
is a C++ Standard Library type. Similarly, your own class types allow finding a swap
function in the namespaces they have been declared in:
namespace N2 {
class MyClass {};
void swap(MyClass&, MyClass&);
}
Therefore, if argument-dependent lookup does not find a better match than pure unqualified lookup, you'll end up calling ns::swap
recursively.
The idea behind calling swap
unqualified, that is, swap(a, b)
instead of std::swap(a, b)
is that functions found via argument-dependent lookup are assumed to be more specialized than std::swap
. Specializing a function template such as std::swap
for your own class template type is impossible (since partial function template specializations are forbidden), and you may not add custom overloads to namespace std
. The generic version of std::swap
is implemented typically as follows:
template<typename T>
void swap(T& a, T& b)
{
T tmp( move(a) );
a = move(b);
b = move(tmp);
}
This requires a move-construction plus two move-assignments, which might even fall back to copies. Therefore, you can provide a specialized swap function for your own types in the namespaces associated with those types. Your specialized version can make use of certain properties of, or private access to, your own types.
The most important piece of the standard is 7.3.4/2 (quoting C++14 n4140, emphasis mine):
A using-directive specifies that the names in the nominated namespace can be used in the scope in which the
using-directive appears after the using-directive. During unqualified name lookup (3.4.1), the names appear
as if they were declared in the nearest enclosing namespace which contains both the using-directive and the
nominated namespace.
The using-directive is located inside a function in :: ns
, and nominates :: std
. This means that for the purpose of unqualified name lookup, the effect of this using-directive is that names in ::std
behave as if they were declared in ::
. Particularly, not as if they were in ::ns
.
Because the unqualified name lookup begins inside a function in ::ns
, it will search ::ns
before looking into ::
. And it finds ::ns::swap
, so it ends there, without examining ::
, where it would find ::std::swap
brought in by the using-directive.