ことはじめ

にほんごのれんしゅうにっき

std::accumulate de overflow

何も考えずに使ってると嵌るよーというお話.

こんなコードを書いたのですが結果がどうもおかしい.

using ull = unsigned long long;
 
std::vector<ull> v = {/* いっぱい */};
ull sum = std::accumulate(v.begin(), v.end(), 0);


うーんうーん唸ってたわけですがライブラリのコードを見れば一発だったわけで...

template <class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init)
{
    for (; first != last; ++first)
        init = init + *first;
    return init;
}

こうなってます.

要は InputIterator::value_type なんか見てないし返り値の型は init のみで決まるよーということ.
なんとなく unsigned long long になってくれるみたいな思い込みで嵌ってしまった例です.

サフィックスつければ解決です. こんなことで嵌るなんて...
他の解決策は関数テンプレートを明示的にインスタンス化するとか init を static_cast するとかですかね.


すごく冗長だけど accumulate の実装をこんな風にすれば問題ない?(decltype~が2つとも同じ...
C++14 なら decltype(auto) が使えるので綺麗になりそうです.

template <class InputIterator, class T>
auto accumulate(InputIterator first, InputIterator last, T init)
-> decltype(std::declval<typename std::iterator_traits<InputIterator>::value_type>() + std::declval<T>())
{
    using RType = decltype(std::declval<typename std::iterator_traits<InputIterator>::value_type>() + std::declval<T>());
    RType ret = init;
    for (; first != last; ++first)
        ret = ret + *first;
    return ret;
}