swapの特殊化

ユーザ定義型に対してswapを特殊化することは良くある.で,このやり方に2通り考えられる.

  • std::swapを特殊化する(#1)
  • その型と同じ名前空間でswapを定義する(#2)

それぞれ,以下のようになる.

namespace my_lib{
  class X{
    .....
  };

  void swap(X &x, X &y) // #2
  {
    .....
  }
}

#include <algorithm> // std::swapのprimary templateの宣言を取り込む

namespace std{
  void swap(my_lib::X &x, my_lib::X &y) // #1
  {
    .....
  }
}

上の場合は#1も#2も問題ない.ところが,ユーザ定義型がテンプレートの場合#1が不可能になる.

namespace my_lib{
  template<class T>
  class X
  {
    .....
  };

  template<class T>
  void swap(X<T> &x, X<T> &y) // #2
  {
    .....
  }
}

#include <algorithm>

namespace std{
  template<class T>
  void swap(my_lib::X<T> &x, my_lib::X<T> &y) // #1:エラー!関数テンプレートの部分特殊化は不可能!
  {
    .....
  }
}

一方でswapを使う関数の側,例えばstd::sortではどのようにswapを呼ぶのかを考えてみる.以下の2つが考えられる.

  • swap(x, y)
  • ::std::swap(x, y)
namespace std{
  template<class RandomAccessIterator>
  void sort(RandomAccessIterator first, RandomAccessIterator last)
  {
    RandomAccessIterator it1, it2;
    .....
    swap(*it1, *it2); // #2 -> #1 -> 汎用のstd::swap の順で採用される
    .....
  }
}
namespace std{
  template<class RandomAccessIterator>
  void sort(RandomAccessIterator first, RandomAccesssIterator last)
  {
    RandomAccessIterator it1, it2;
    .....
    ::std::swap(*it1, *it2); // #1 -> 汎用のstd::swapの順で採用される(#2はLook Upの対象から外れる)
    .....
  }
}

対象がテンプレートの場合#2が必要になることから考えて前者が採用されてしかるべきだろう.現在の規格ではこれは保証されていないが将来的には保証されるみたい.
ちなみにstd名前空間以外の場所で汎用関数からswapを呼ぶ場合には一工夫必要になる.

namespace my_lib{
  template<class T>
  void f()
  {
    T x, y;
    .....
    using std::swap;
    swap(x, y); // #2 -> #1 -> 汎用のstd::swapの順で採用される
    .....
  }
}

using std::swapが無いとTが組み込み型のとき困ることになる.

その他,細かいこと

上についてのより詳細な議論として以下が参考になる.
http://tinyurl.com/6mhad
(comp.std.c++ 2004/07/18~ "Whence swap?")
上で指摘しているswapの規格の問題は以下.
http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#226
これに対するproposalは以下.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1523.htm
また,swapのLook Upの問題とは逆にADLによって意図しない関数がLook Upされる問題.
http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#225
これに対するproposalとしては,常に::std::で限定するというもの.
ちなみに,以上の問題の解決の一方向性として想起される関数の部分特殊化(テンプレートに対しても#1を可能にする)について,それが許されていないことに対する議論は以下.
http://www.gotw.ca/publications/mill17.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2001/n1295.asc


以上から考える,ライブラリ内における自由関数呼び出しの指針.

  • クライアントの特殊化を前提とした関数を呼び出す場合:名前空間の修飾無しで呼び出す.他の名前空間にある自由関数の場合usingと併用する.ただし,これは暗黙のうちにクライアントの名前空間を汚していることになるので,ドキュメントで明確に言及しなければならない.
  • クライアントの特殊化を前提としない関数を呼び出す場合:名前空間の修飾をつけて呼び出す.