copy_n
copy_n()という関数は標準にはない*1。fill_nはあるのに。なんでないのか?その理由は良くわかってない。
copy_nの存在価値はあるか?あるとしたら最適化の考慮のためだろう。
copy_nは条件次第で最適化により高速な関数memmoveまたはmemcpyに置き換える。
このときこれらの関数の引数は
void *memcpy( void *to, const void *from, size_t count );
といったようにコピーする数を指定するのでcopyよりcopy_nによりマッチする。
copy_nの引数であるイテレータが以下の条件をクリアする必要がある。
一番目の条件に対しては
boost::is_pod< typename iterator_traits<IT>::value_type >::value//trueならOK
で検出できる。is_podは現行のコンパイラでは正確には動作しない。なぜってPODには単純な構造体・共有型などの複合型も含まれるからだ。まあ組み込み型*2だったらOKってことでもいいんじゃないかと思う。
2番目の条件がむずかしい。
これはつまりイテレータの指している先がメモリ上で連続でなくてはいけない。IT型の任意のイテレータiterにおいて以下のことを保障しなくてはならない。
&(*(iter++)) == (&(*iter))++
このイテレータの性質を静的に判断することは、そのイテレータがポインタであること以外にはできない。かといって上記の検査を全てのイテレーションで行うのは意味がない。
では標準ではどうしているか?手元のMinGW・gccの該当部分を見てみると条件を満たすイテレータは__Normal_iteratorなるものから継承される。その上でアルゴリズムcopyなどはこれを検出し特別化している。しかし__Normal_iteratorは標準ではない。
実際、標準のcopyはイテレータがメモリ連続であることを保障した(__Normal_iteratorを継承した)標準コンテナのイテレータとポインタ以外は最適化できない。
最後にmemcpy、memmoveどちらにすべきかという問いに対しては入力イテレータと出力イテレータのさす範囲が重なっていたらmemmoveそうでなかったらmemcpyということができる。でもこれは実行時にしか判断できない。つまりif分による分岐が必要なわけだが、memmoveとmemcpyの速度差を考えるとコストがでかすぎる。よほどでかい配列のコピーでもなければ。
fillとfill_nのことも考える。これらの関数が最適化により高速な関数に置き換わるとすれば
void *memset( void *buffer, int ch, size_t count );
だろう。この関数は初期値指定にint型の値を指定する。しかしコピー数を指定するcountが1バイトの大きさを基準にしている。
このため標準ではイテレータがchar*またはunsigned char*であるときにのみ最適化をしている。そのほかの組み込み型のポインタでは最適化はできない*3。
さてfillを使いたいとき、かなりの割合で配列を0で初期化したい場合(O埋め)なんじゃないかなと思う。
で上記の理由からchar型以外の配列には最適化が効かない。しかし整数型のすべては0は0(すべてのビットが0)である。また浮動小数点型においてもそれがIEEE754規格にそっている限り0は0である。
このことから0埋め用の関数を作っておくと便利かもしれない。memsetが使える条件は
である。1番目2番目の条件からイテレータは組み込み型のポインタでなければならない。3番目の条件はnumeric_limits
template<class T> struct is_void<T>{static const bool value = false;} template<> struct is_void<void>{static const bool value = true;} template<class T> struct is_pointer<T>{static const bool value = false;} template<class T> struct is_pointer<T*>{static const bool value = true;} template<class IT,bool B = true> struct fill_zero_n_imp{ inline static void func(IT first,size_t n){ typedef typename std::iterator_traits<IT>::value_type value_type; std::memset(reinterpret_cast<void*>(first),0,n*sizeof(value_type)); } }; template<class IT> struct fill_zero_n_imp<IT,false>{ inline static void func(IT first,size_t n){ typedef typename std::iterator_traits<IT>::value_type value_type; value_type temp = value_type(); while(n--){ *first = temp; ++first; } } }; template<class IT,class Sz> inline void fill_zero_n(IT first,Sz n){ typedef typename std::iterator_traits<IT>::value_type value_type; typedef std::numeric_limits<value_type> NL; static const bool is_optimize = (is_pointer<IT>) & ((NL::is_integer)|(NL::is_iec559)|(is_void<value_type>::value)|(is_pointer<value_type>::value)); fill_zero_n_imp<value_type,is_optimize>::func(first,n); }
まあそこまで最適化にこだわるのはアレだと思うが、案外最適化されないことって多いんだと思う。