9

I am studying this fascinating answer to a subtle question regarding the best practice to implement the swap function for user-defined types. (My question was initially motivated by a discussion of the illegality of adding types to namespace std.)

I will not re-print the code snippet from the above-linked answer here.

Instead, I would like to understand the answer.

The answer I've linked above states, beneath the first code snippet, in regards to overloading swap in namespace std (rather than specializing it in that namespace):

If your compiler prints out something different then it is not correctly implementing "two-phase lookup" for templates.

The answer then goes on to point out that specializing swap in namespace std (as opposed to overloading it) produces a different result (the desired result in the case of specialization).

However, the answer proceeds with an additional case: specializing swap for a user-defined template class - in which case, again, the desired result is not achieved.

Unfortunately, the answer simply states the facts; it does not explain why.

Can someone please elaborate on that answer, and describe the process of lookup in the two specific code snippets provided in that answer:

  • overloading swap in namespace std for a user-defined non-template class (as in the first code snippet of the linked answer)

  • specializing swap in namespace std for a user-defined template class (as in the final code snippet of the linked answer)

In both cases, the generic std::swap is called, rather than the user-defined swap. Why?

(This will shed light on the nature of two-phase lookup, and the reason for the best practice for implementing user-defined swap; thanks.)

Community
  • 1
  • 1
Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181
  • "However, the answer proceeds with an additional case: specializing swap for a user-defined template class - in which case, again, the desired result is not achieved." -- Are you sure that's specializing? It looks to me like that's overloading again, which is also why it won't work (the overloaded function wasn't in scope where `swap` was called, and ADL doesn't find it because there's no associated namespace `std` to trigger a search in that namespace). (This is too brief to be a full answer, but it may help get you started on understanding.) –  Jan 27 '14 at 15:34
  • 1
    As for :overloading `swap` in `namespace std`": it is undefined behavior. The first code snippet of the linked answer examines what would happen *if* it where defined behavior and it acted just like any other `namespace` by using a different `namespace` (`exp` for experimental). – Yakk - Adam Nevraumont Jan 27 '14 at 15:35
  • @Yakk - understood. The assumption here is that is *would be* possible to define additional types in `namespace std` (even though it isn't - for reasons unrelated to the question). I could have used `namespace exp` in my question, but somehow I think that wouldn't draw the same (hopefully proper) attention. – Dan Nissenbaum Jan 27 '14 at 15:37
  • 1
    @hvd Good point. How *would* one **specialize** `swap` for a template class, as opposed to **overload**? Is that even possible? – Dan Nissenbaum Jan 27 '14 at 15:41
  • 1
    @DanNissenbaum Partial specializations of functions are not possible, unless C++11 changed this (and as far as I know, it didn't). –  Jan 27 '14 at 15:45
  • @hvd - What about a *complete* specialization of `swap`, but specializing it for a *template* class? – Dan Nissenbaum Jan 27 '14 at 15:46
  • @hvd Regarding your initial comment - I'd think that `exp::algorithm` wouldn't be *instantiated* by the compiler until the *main* function is compiled, at which time the overloaded version of `swap` *is* in scope - where am I going wrong? Thanks. – Dan Nissenbaum Jan 27 '14 at 16:00
  • Did you read the [link in the same answer](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2001/n1289.html)? – Shahbaz Jan 27 '14 at 16:21
  • @Shahbaz I've just read it. Fascinating. I still do not see whether the current *accepted* approach, given in http://stackoverflow.com/a/2684544/368896, involves acceptance of the proposal by in the link you've provided. – Dan Nissenbaum Jan 27 '14 at 16:40

2 Answers2

8

Preamble with plenty of Standardese

The call to swap() in the example entails a dependent name because its arguments begin[0] and begin[1] depend on the template parameter T of the surrounding algorithm() function template. Two-phase name lookup for such dependent names is defined in the Standard as follows:

14.6.4.2 Candidate functions [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 (3.4.1, 3.4.2) except that:

— For the part of the lookup using unqualified name lookup (3.4.1), only function declarations from the template definition context are found.

— For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.

Unqualified lookup is defined by

3.4.1 Unqualified name lookup [basic.lookup.unqual]

1 In all the cases listed in 3.4.1, the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name. If no declaration is found, the program is ill-formed.

and argument-dependent lookup (ADL) as

3.4.2 Argument-dependent name lookup [basic.lookup.argdep]

1 When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function or function template declarations (11.3) not otherwise visible may be found. These modifications to the search depend on the types of the arguments (and for template template arguments, the namespace of the template argument).

Applying the Standard to the example

The first example calls exp::swap(). This is not a dependent name and does not require two-phase name lookup. Because the call to swap is qualified, ordinary lookup takes place which finds only the generic swap(T&, T&) function template.

The second example (what @HowardHinnant calls "the modern solution") calls swap() and also has an overload swap(A&, A&) in the same namespace as where class A lives (the global namespace in this case). Because the call to swap is unqualified, both ordinary lookup and ADL take place at the point of definition (again only finding the generic swap(T&, T&)) but another ADL takes place at the point of instantiation (i.e where exp::algorithm() is being called in main()) and this picks up swap(A&, A&) which is a better match during overload resolution.

So far so good. Now for the encore: the third example calls swap() and has a specialization template<> swap(A&, A&) inside namespace exp. The lookup is the same as in the second example, but now ADL does not pick up the template specialization because it is not in an associated namespace of class A. However, even though the specialization template<> swap(A&, A&) does not play a role during overload resolution, it is still instantiated at the point of use.

Finally, the fourth example calls swap() and has an overload template<class T> swap(A<T>&, A<T>&) inside namespace exp for template<class T> class A living in the global namespace. The lookup is the same as in the third example, and again ADL does not pick up the overload swap(A<T>&, A<T>&) because it is not in an associated namespace of the class template A<T>. And in this case, there is also no specialization that has to be instantiated at the point of use, so the generic swap(T&, T&) is being callled here.

Conclusion

Even though you are not allowed to add new overloads to namespace std, and only explicit specializations, it would not even work because of the various intricacies of two-phase name lookup.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Is the second example you're referring to the one involving implementing `swap` for a **template** class? (It looks like your answer is in regards to the `void swap(A&, A&) {}` *global* function, which is in the *middle* of the linked answer - My second bullet point asks about the `template void swap(A&, A&) {}` definition in `namespace exp`, which is towards the *end* of the linked answer.) – Dan Nissenbaum Jan 27 '14 at 15:50
  • Thanks. Regarding the *first* case (where ADL is disabled) - ADL may be disabled, but the `exp::algorithm` function is only *instantiated* by the compiler when the *main* function is being compiled, right? - in which case, I'd think the overloaded version of `swap` would be in scope. (Where am I going wrong?) – Dan Nissenbaum Jan 27 '14 at 15:55
  • @DanNissenbaum will have to rewrite this, deleting until further notice. – TemplateRex Jan 27 '14 at 15:57
  • @DanNissenbaum I think the explanation is complete now, see updated answer. – TemplateRex Jan 27 '14 at 19:43
  • @dyp I would assume so... Consider this example: `namespace exmpl { template class A { public: A(T&& t_) : t(t_) {} private: T t; }; }`. In this example, I'd think that `exmpl::A` is a dependent name (it depends on `T`). Does that sound right? – Dan Nissenbaum Jan 27 '14 at 22:45
  • @DanNissenbaum `exmpl::A` itself is an id-expression, the name `A` in it does not depend on template parameters in any form (it names the template itself). I think it's not dependent. Edit: where does `exmpl::A` appear in your example? Or do you mean `A` w/o the `exmpl`, inside the class template? – dyp Jan 27 '14 at 22:49
  • @dyp oops, `exp::swap` is indeed not dependent, although in this case it would not matter for lookup. Tnx and updated! – TemplateRex Jan 28 '14 at 16:17
4

It is impossible to overload swap in namespace std for a user defined type. Introduction an overload (as opposed to a specialization) in namespace std is undefined behavior (illegal under the standard, no diagnosis required).

It is impossible to specialize a function in general for a template class (as opposed to a template class instance -- ie, std::vector<int> is an instance, while std::vector<T> is the entire template class). What appears to be a specialization is actually an overload. So the first paragraph applies.

The best practice for implementing user-defined swap is to introduce a swap function or overload in the same namespace as your template or class lives in.

Then, if swap is called in the right context (using std::swap; swap(a,b);), which is how it is called in std library, ADL will kick in, and your overload will be found.

The other option is to do a full specialization of swap in std for your particular type. This is impossible (or impractical) for template classes, as you need to specialize for each and every instance of your template class that exists. For other classes, it is fragile, as specialization applies to only that particular type: subclasses will have to be respecialized in std as well.

In general, specialization of functions is extremely fragile, and you are better off introducing overrides. As you cannot introduce overrides into std, the only place they will be reliably found from is in your own namespace. Such overrides in your own namespace are preferred over overrides in std as well.

There are two ways to inject a swap into your namespace. Both work for this purpose:

namespace test {
  struct A {};
  struct B {};
  void swap(A&, A&) { std::cout << "swap(A&,A&)\n"; }
  struct C {
    friend void swap(C&, C&) { std::cout << "swap(C&, C&)\n"; }
  };

  void bob() {
    using std::swap;
    test::A a, b;
    swap(a,b);
    test::B x, y;
    swap(x, y);
    C u, v;
    swap(u, v);
  }
}

void foo() {
  using std::swap;
  test::A a, b;
  swap(a,b);
  test::B x, y;
  swap(x, y);
  test::C u, v;
  swap(u, v);

  test::bob();
}
int main() {
  foo();
  return 0;
}

the first is to inject it into the namespace directly, the second is to include it as an inline friend. The inline friend for "external operators" is a common pattern that basically means you can only find swap via ADL, but in this particular context does not add much.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • This is an excellent answer. Thanks. It doesn't *directly* answer my question, though (unless I'm missing something obvious). – Dan Nissenbaum Jan 27 '14 at 18:25
  • 1
    @DanNissenbaum There are two parts to your question. One is about overriding functions in namespace `std`. That is undefined behavior, as I noted above. The second is about (partial) specialization of functions in namespace `std`. That is impossible in C++, there are no partial specializations of functions. Syntax that looks like it is partial specialization is actually overriding, which falls back to undefined behavior. The question you may have wanted to ask was about `namespace exp`? In that case, I'd ask a different question, and not mention `std` at all. – Yakk - Adam Nevraumont Jan 27 '14 at 18:46
  • Please allow me to *pretend* that it's allowed to overload functions in `namespace std` (because the reason this is disallowed is unrelated to my question, I think) - instead, imagine my question was framed using `namespace exp`, as in the linked answer, but is otherwise an identical question. I only used `namespace std` to draw a relevant focus to the question. Thanks! – Dan Nissenbaum Jan 27 '14 at 18:53
  • @Yakk see my answer for why it wouldn't work even if allowed (say in a `namespace exp`, as in the linked answer by Hinnant). – TemplateRex Jan 27 '14 at 19:47