テンプレートの部分特殊化を利用した間接参照型推定

C++の機能,テンプレートの部分特殊化 (Wikipedia)を使って,あるポインタ型orイテレータ型から,それを間接参照した(or逆参照した,orポインタをとっぱらった)型を推定する方法を紹介する.
例えば,過去の記事d:id:aki-yam:20081024:1224846960で,和を求めるテンプレート関数

template <typename T, typename ForwardIterator>
inline T sum (ForwardIterator first, ForwardIterator last, const T &zero=0.0l)
{
  T res(zero);
  if (first==last)  return res;
  for (; first!=last; ++first)
    res += *first;
  return res;
}

を紹介した.この関数は少なくとも STL コンテナ,配列,liboctave の ColumnVector に対して共通に使用することができた.

さて,このテンプレートで, "typename T" ははたして必要なのだろうか? つまり, ForwardIterator first として宣言されている first (これはイテレータであったりポインタであったりする)を間接参照した型が T でなければならないから, *first の型が分かれば T を明示する必要が無くなるはずなのだ.しかし,現行の C++ にはインスタンスの型を取得する方法は無い*1C++0x では可能になるようだが(C++0x インスタンスからメンバの型を取得するあたりを参照),それは未来の話.

そこで,応急処置的な代用方法として,上の ForwardIterator 型を間接参照した型を取得する方法を考えてみた.一番シンプルに実現できるのが,おそらくテンプレートの部分特殊化 (Wikipedia)を使った方法で,具体的にはこうする:

template <typename T>
struct dereferenced_type
{
  typedef typename T::value_type type;
};
template <typename T>
struct dereferenced_type<T*>
{
  typedef T type;
};

template <typename FwdItr>
inline typename dereferenced_type<FwdItr>::type
    sum (FwdItr first, FwdItr last, const typename dereferenced_type<FwdItr>::type &zero=0.0l)
{
  typename dereferenced_type<FwdItr>::type res(zero);
  if (first==last)  return res;
  for (; first!=last; ++first)
    res += *first;
  return res;
}

ひとつ目のテンプレートクラス dereferenced_type は, T がイテレータクラスであることを仮定している.すべてのイテレータクラスでは, value_type 型が内部で定義されているから, T::value_type はイテレータを間接参照した型を表す.よって, dereferenced_type::type で,イテレータ T を間接参照した型が得られるというわけだ.
ただし,これだけだと T=int* のような場合に対処できない.そこで,ふたつ目の dereferenced_type によって,もとの dereferenced_type の部分特殊化を行っている.これは型 T のポインタ型に対する特殊化である.この場合, T* を逆参照した型は当然 T であるから, dereferenced_type::type で, T が得られることになる.
最後のテンプレート関数がこれを利用した sum の定義だ. dereferenced_type::type が FwdItr を間接参照した型を表している.このようにして,d:id:aki-yam:20081024:1224846960で紹介した sum から "typename T" を取り去ることに成功した.

ちなみに,上の sum を利用するには,d:id:aki-yam:20081024:1224846960にあるサンプルから T を指定する部分,すなわち などを取り去ればよい.例えば

  cout<<"sum(x)= "<<sum(x.fortran_vec(),x.fortran_vec()+x.length())<<endl;
  cout<<"sum(y)= "<<sum(y.begin(),y.end())<<endl;
  cout<<"sum(z)= "<<sum(z,z+sizeof(z)/sizeof(z[0]))<<endl;

のようにすれば利用できる.

余談だが, boost にもポインタを取り去る remove_pointer というテンプレートクラスが存在する. boost/type_traits/remove_pointer.hpp か boost/type_traits.hpp を include すれば使える.が,上記 dereferenced_type の代わりに使ってもうまく機能しなかった.イテレータを間接参照した型を取得するようにはできていなかったためだ.

*1:typeid は type_info 型のオブジェクトを返すが,それを使ってそのインスタンスの型と同じオブジェクトを生成できない.