accumulator_set と std::for_each

Boost 1.36 からある boost::accumulators::accumulator_set は統計を取るクラス (参照:letsboost::accumulators)。参照先にあるような例題では要素数が少ないのでそのまま書き下せるが、要素数が増えてくるとループで回すことになる。例えば以下のように。

for (int i = 0; i < 1000; ++i)
  acc(array[i]);

さて、ループ処理において accumulators_set を std::for_each と組み合わせられるだろうか?という話。

#include <algorithm>
#include <ostream>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
int main() {
 double array[1000] = {0};
 initialize(array);  // 値を適当に入れる。
 using namespace std;
 using namespace boost::accumulators;
 // 最小値と平均値と中間値が知りたい
 accumulator_set< double, features<tag::min, tag::mean, tag::median> > acc;
 // データ集計
 std::for_each(&array[0], &array[sizeof(array)/sizeof(double)], acc);
 // 結果
 cout << extract::min(acc) << endl;
 cout << extract::mean(acc) << endl;
 cout << extract::median(acc) << endl;
}

結論から言うと、このコードでは正しい結果が出ない。std::for_each に渡されるのは acc のコピーであり、参照ではないからだ。参照渡しにするためには、テンプレート関数に対して明示的に型を指定する必要がある。

std::for_each<double*, accumulator_set< double,
features<tag::min, tag::mean, tag::median> >&>(&array[0],
&array[sizeof(array)/sizeof(double)], acc);

型の指定が激しくめんどくさくなるので、BOOST_TYPEOF (GCC では typeof 演算子)を使うと少しはマシになる。

#include <boost/typeof/typeof.hpp>
:
std::for_each<BOOST_TYPEOF(&array[0]), BOOST_TYPEOF(acc)&>(&array[0],
&array[sizeof(array)/sizeof(double)], acc);
:

これも普通に for 文で回すより可読性はよくない。しかし、STLコンテナなどイテレータを使う際には for 文で書くよりも使いやすいだろう。また、STLを使うメリットは OpenMP による並列化もあるのでやっぱりこの方法を使おう。
なお、参照渡しを作る boost::ref を使う方法はこの場合にはコンパイルエラーとなる。

#include <boost/ref.hpp>
:
std::for_each(&array[0],
&array[sizeof(array)/sizeof(double)], boost::ref(acc));
:

コンパイルエラーは以下のような感じ。

/include/c++/bits/stl_algo.h:3791: error: no match for call to
(boost::reference_wrapper<boost::accumulators::accumulator_set <float,
boost::accumulators::stats<boost::accumulators::tag::min,
boost::accumulators::tag::max, boost::accumulators::tag::mean,
boost::accumulators::tag::variance,
mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na,
mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na, mpl_::na,
mpl_::na, mpl_::na, mpl_::na, mpl_::na>, void> >) (double&)

関数 std::for_each 内で関数オブジェクトの()演算子 acc(value) を呼び出せないというエラー。まあこれは boost::ref の制限かな多分…。