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.
using namespace std;
behave a very peculiar behaviour; they're not using-declarations such asusing std::swap
. – Heterodoxstd
visible as if they're declared in the global namespace here, but the nameswap
in the global namespace is hidden for pure unqualified lookup insidens::swap
because the namespacens
already contains a member of the nameswap
. – Heterodoxusing std::swap;
, we tell it to stop at block scope -ns::swap
will not be found, it is a member of the enclosing namespace scope. Argument-dependent lookup on the other hand searches all scopes associated with the types of the arguments. – Heterodox