7

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.

Community
  • 1
  • 1
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • 2
    Note that using-directives such as `using namespace std;` behave a very peculiar behaviour; they're not using-declarations such as `using std::swap`. – dyp May 04 '15 at 14:15
  • @dyp Just had a deep feeling about that :-) – Lingxi May 04 '15 at 14:18
  • As far as I can tell, this using-directive won't do anything. It makes names of namespace `std` visible as if they're declared in the global namespace here, but the name `swap` in the global namespace is hidden for pure unqualified lookup inside `ns::swap` because the namespace `ns` already contains a member of the name `swap`. – dyp May 04 '15 at 14:18
  • So, if I understand correctly, if you change it to using std::swap, we'll get compilation error since compiler will see both std::swap and ns::swap. – Anton Frolov May 04 '15 at 14:22
  • @AntonFrolov There are two mechanisms for name lookup involved here: pure unqualified lookup and argument-dependent lookup. Pure unqualified lookup will stop in the next enclosing scope where it finds the name. With `using 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. – dyp May 04 '15 at 14:25

2 Answers2

6

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.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • 1
    You were both faster and more detailed, +1. – Angew is no longer proud of SO May 04 '15 at 15:13
  • @Angew I probably just started earlier ;) – dyp May 04 '15 at 15:13
  • I just thought overloading `std::swap` for custom types is standard practice. – Lingxi May 05 '15 at 01:24
  • 1
    @Lingxi Programs may not add definitions or declarations to the `std` namespace "unless otherwise specified" [namespace.std]p1. There is no such exception for `std::swap`. The common practice I know of is to define `swap` as a non-member friend function, possibly defined inside the class body. See also http://stackoverflow.com/a/5695855/ Types defined by the C++ Standard Library do overload `std::swap`, but they're in the unique position of being part of the Standard - and `std` is the namespace they're associated with (so this follows the aforementioned common practice). – dyp May 05 '15 at 01:34
2

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.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455