Why will two-phase lookup fail to choose overloaded version of 'swap'?

后端 未结 2 1977
我在风中等你
我在风中等你 2021-01-04 22:15

I am studying this fascinating answer to a subtle question regarding the best practice to implement the swap function for user-defined types.

2条回答
  •  -上瘾入骨i
    2021-01-04 22:38

    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 is an instance, while std::vector 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.

提交回复
热议问题