std::vector, liboctave の ColumnVector, 配列に共通の関数

STL (標準テンプレートライブラリ) の std::vector とか liboctave の ColumnVector,あるいは普通の配列に対して共通に使えるテンプレート関数の作り方を説明する.


ポイントは

  1. テンプレートを使うことと,
  2. ColumnVector の fortran_vec() メンバ関数を使うこと

の2点.テンプレートについては,知っていることを前提とします(or 適当な書籍を参照).

ColumnVector の fortran_vec() は

const double* ColumnVector::fortran_vec (void) const;

という関数で*1, ColumnVector のデータが保存されているメモリ領域へのポインタを返す.つまり, ColumnVector::length() 個の要素を持つ double 型配列の先頭ポインタと同等のものを返す.

よって,例えばある「ベクタ」の和を取るテンプレート関数は,次のように作ればよい:

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;
}

ここで T は double など,ベクタの要素の型を表す. ForwardIterator は,インクリメント演算子 (++) によって順番に要素に順番に要素にアクセスでき,間接演算子 (*) によって値を参照できる型であれば何でもよい.例えばポインタや, std::vector::iterator などが該当する.

この関数を使うには,

ColumnVector   x(10);
vector<double> y(10);
double         z[10];

のそれぞれに対し,

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

のように使う. ForwardIterator の型は自動的に判定されるから明示していない.

サンプルプログラム (g++ でテスト):

#include <iostream>
#include <vector>
#include <cstdlib>
#include <octave/config.h>
#include <octave/Matrix.h>

inline double u_rand (const double &max)
{
  return (max)*static_cast<double>(rand()) / static_cast<double>(RAND_MAX);
}
inline double u_rand (const double &min, const double &max)
{
  return u_rand(max - min) + min;
}

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;
}

template <typename T>
struct vec2
{
  T x,y;
  vec2(void) : x(0),y(0) {};
  vec2(T _x, T _y) : x(_x), y(_y) {};
  const vec2& operator+=(const vec2 &rhs)
    {x+=rhs.x; y+=rhs.y; return *this;};
};
template <typename T>
inline std::ostream& operator<< (std::ostream& lhs, const vec2<T> &x)
{
  lhs<<"("<<x.x<<", "<<x.y<<")";
  return lhs;
}

using namespace std;

int main(int argc, char**argv)
{
  srand((unsigned)time(NULL));
  ColumnVector   x(10);
  vector<double> y(10);
  double         z[10];
  vec2<double>   w[10];
  for(int r(0);r<x.length();++r)
    x(r)=y[r]=z[r]=w[r].x=w[r].y= u_rand(-5.0,5.0);
  cout<<"x= "<<x.transpose()<<endl;
  cout<<"sum(x)= "<<sum<double>(x.fortran_vec(),x.fortran_vec()+x.length())<<endl;
  cout<<"sum(y)= "<<sum<double>(y.begin(),y.end())<<endl;
  cout<<"sum(z)= "<<sum<double>(z,z+sizeof(z)/sizeof(z[0]))<<endl;
  cout<<"sum(w)= "<<sum(w,w+sizeof(w)/sizeof(w[0]),vec2<double>(0.0,0.0))<<endl;
  return 0;
}

サンプルには,(x,y) の二つの要素を持つテンプレートクラス vec2 の和の例も盛り込んである.この場合は sum 関数の第3引数 zero を明示する必要がある一方, sum の T 型は zero から判断できるから省略してもいい.

追記 (2008/11/10)

手軽に利用するために,

inline double*  CVbegin (ColumnVector &vec)
{
  return vec.fortran_vec();
}
inline const double*  CVbegin (const ColumnVector &vec)
{
  return vec.fortran_vec();
}
inline double*  CVend (ColumnVector &vec)
{
  return vec.fortran_vec()+vec.length();
}
inline const double*  CVend (const ColumnVector &vec)
{
  return vec.fortran_vec()+vec.length();
}

のような関数群を用意しておくとよい.

*1:実は Array テンプレートクラスのメンバだが,ややこしいから簡略化している.