2016-09-09 14 views
0

は、以下のコードを見てみましょう:ラムダ関数型クリープ

tbb::blocked_range<int> range(0, a.rows); 
uint64_t positive = tbb::parallel_reduce(range, 0, // <- initial value 
    [&](const tbb::blocked_range<int>& r, uint64_t v)->uint64_t { 
    for (int y = r.begin(); y < r.end(); ++y) { 
     auto rA = a[y], rB = b[y]; 
     for (int x = 0; x < a.cols; ++x) { 
      auto A = rA[x], B = rB[x]; 
      for (int l = y; l < a.rows; ++l) { 
       auto rAA = a[l], rBB = b[l]; 
       for (int m = x; m < a.cols; ++m) { 
        if (l == y && m == x) 
         continue; 
        auto AA = rAA[m], BB = rBB[m]; 
        if ((A == AA) && (B == BB)) 
         v++; // <- value is changed 
        if ((A != AA) && (B != BB)) 
         v++; // <- value is changed 
       } 
      } 
     } 
    } 
    return v; 
}, [](uint64_t first, uint64_t second)->uint64_t { 
    std::cerr << first << ' + ' << second; // <- wrong values occur 
    return first+second; 
} 
); 

は、これは、初期値が初期値に基づいて、各並列計算で、その後は0である並列減らす操作で、私たちをカウントアップする(最初のラムダ関数のローカル変数v)。 2番目のラムダ関数は、並列作業者からの結果を集計します。

興味深いことに、このコードはではなく、が正常に動作します。 2番目のラムダ関数の出力には、整数のオーバーフローによる膨大な数値が表示されます。

uint64_t positive = tbb::parallel_reduce(range, (uint64_t)0, // <- initial value 

は今、私は疑問に思う:と二行目の交換時

コードが正常に動作します。最初のラムダ(uint64_t v)の定義はこのキャストを強制しないでしょうし、uint64_tで動作するはずの関数はどうやってintで動作するのでしょうか?

コンパイラはGCC 6です。

+0

'0'は 'int' ... – Jarod42

+0

確実なことです。しかし、引数 'v'は' uint64_t'です。 – ypnos

答えて

3

ラムダがどのような引数を取るかは関係ありません。 the docsによると、すべてが第二引数の型に基づいています:

template<typename Range, typename Value, 
     typename Func, typename Reduction> 
Value parallel_reduce(const Range& range, const Value& identity, 
         const Func& func, const Reduction& reduction, 
         [, partitioner[, task_group_context& group]]); 

の擬似署名で:

Value Func::operator()(const Range& range, const Value& x) 
Value Reduction::operator()(const Value& x, const Value& y) 

のでValueFuncへとReductionに渡され、返されます。どこでもuint64_tが必要な場合は、Valueuint64_tであることを確認する必要があります。あなたの(uint64_t)0は動作しますが、0は動作しません(そして実際には起動するための動作は未定義です)。これはあなただけの通常のaccumulateになるだろうと同じ問題であることを


注:

std::vector<uint64_t> vs{0x7fffffff, 0x7fffffff, 0x7fffffff}; 
uint64_t sum = std::accumulate(vs.begin(), vs.end(), 0, std::plus<uint64_t>{}); 
           //     ^^^ oops, int 0! 
           //   even though I'm using plus<uint64_t>! 
assert(sum == 0x17ffffffd);  // fails because actually sum is truncated 
           // and is just 0x7ffffffd 
+2

実際、上記の例の結果はUB(符号なし - >符号付きオーバーフローはUB)です。 – Yakk

+0

これを単純な方法で要約すると、ラムダ関数は実際にはuint64_tで計算されますが、intに暗黙的に定義されたインターフェイスにラップされます。したがって、最初のラムダの返り値にint型へのキャストがありますintは2番目のラムダに渡され、次にuint64_tにリキャストされますが、ダメージは既に行われています。 – ypnos

+0

@ypnosええ、それは基本的にそれです。 – Barry