LoginSignup
3
2

More than 1 year has passed since last update.

【C++】和をaccumulate/reduce()で算出するときにオススメするお作法

Last updated at Posted at 2021-09-05

はじめに

std::accumulate/reduce()には、「第三引数に生の0を渡すと、intとして誤集計してしまう」という罠があります。
競プロでもプロダクトでも、これが発生するととても辛い気持ちになりそうです。
それにもかかわらず、これを防ぐ方法が日本語で見当たらなかったので、この記事を書きました。

現状広く知れ渡ったお作法ではありませんが、accumulate()を書いている全人類にオススメしたいです。

(2021.9.11 追記) C++17が書ける環境では黙ってreduce()を使いましょう。@yumetodoさんのコメントに感謝。

    float v[] = {1.1, 1.2, 1.4};
    // std::vector<float> v = {1.1f, 1.2f, 1.4f};
    // std::array v = {1.1f, 1.2f, 1.4f};
    float sum = std::reduce(std::begin(v), std::end(v)); // どの型でも無事3.7
    std::cout << sum << std::endl;

以下はC++17が使えず、C++11が使える人向けの記事です。

お作法

accumulateの第三引数には、std::remove_refrerence~以下の部分を渡します。

  vector<int> v = {1, 2, 4};
  int sum = accumulate(v.begin(), v.end(), std::remove_reference<decltype(*std::begin(v))>::type{}); // => 7

以上です。
std::vector以外にも普通の配列, std::*set, std::arrayでも使えます。

accumulate の罠

お作法に従わないと以下の罠にハマる可能性があります。

悪い例
  vector<float> v = {1.1, 1.2, 1.4};
  float sum = accumulate(v.begin(), v.end(), 0); // 3.7になるはずが、3が出力される!

理由は、初期値に生の0を渡したせいで、リテラル的にintと解釈され、floatがintとして誤集計されてしまうからです。

この意図しないダウンキャストを防ぐために、decltype(v)::value_type(0)を渡すというのが今回のお作法です。

お作法を使った例
  vector<float> v = {1.1, 1.2, 1.4};
  float sum = accumulate(v.begin(), v.end(), std::remove_reference<decltype(*std::begin(v))>::type{}); // 無事3.7

これは「vというコンテナの要素型を0で初期化した値」で、実質(float) 0を渡したことになります。よって、floatとして集計され、無事に3.7が出力されました。

float以外の事例では、int64_tlong longをintとして誤集計してしまいオーバーフローしたといったことがあるようです。

std::arrayじゃない方の静的配列の場合

stdじゃない方のarrayはdecltype()が使えないので、残念ながらお作法が適用できません。

(2021.9.11 追記)
decltype(v)::value_type(0)remove_reference<decltype(std::begin(v))>::type{} とすることで対応できました。

おわりに

C++強い勢はC++が強い

参考

3
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2