QtConcurrent::mappedReduced

前回、QtConcurrentのmapを取り上げたので、mappedReducedも試してみようと思ったら、すごくはまったと言う話。

ドキュメントを見てもさっぱり使い方がわからないので、Examplesディレクトリを探してみたら、qtconcurrent/wordcount と言うのがmappedReduced()のサンプルになっているらしい。
map-reduceで良く出てくるwordcoundの例になっている。

簡単のために、今回もblocking版を使うことにして、マニュアルによると以下のシグネチャになっている。(iterator版もあるけどここでは割愛)

```cpp
T QtConcurrent::blockingMappedReduced(const Sequence & sequence, MapFunction mapFunction, ReduceFunction reduceFunction, QtConcurrent::ReduceOptions reduceOptions = UnorderedReduce | SequentialReduce)
```

この関数がやってくれることは、sequenceの一つ一つの要素に対して、mapFunctionを呼び出し、mapFunctionの結果に対してreduceFunctionを呼び出し、全てのreduceFunctionの呼び出しが終わったら結果を返す。

wordcountのサンプルを読んで理解した使い方は以下のような感じ。
* 以下の3つの型が登場する
- Sequenceの要素の型(Aとする)
- MapFunctionの返値の型(Bとする)
- mappedReducedの結果の型(Cとする)
* MapFunctionは、`const A&`を引数に取り`B`を返す
* ReduceFunctionは、第1引数に`C&`, 第2引数に`const B&`を取り返値はvoid
* MapFunctionは並列に呼び出される
* ReduceFunctionはreduceOptionsに従い呼び出されるが、Qt 5.4の時点では SequentialReduceしかないので、シングルスレッドで呼び出される(将来的には並列実行できるようになるらしい)

# 駄目な例
wordcountのサンプルを使うとコードが長くなってしまうので、以下のような例を考える。(まったくMapReduceの例として相応しくないが、説明のため)
* Sequenceは`QList<int>`
* MapFunctionは、引数を2倍して返す
* ReduceFunctionは、MapFunctionの結果を合計する

前回と同じようにlambdaを使ってコードにすると以下のような感じ。

```cpp
QList<int> list;
for (int i = 1; i <= 10; i++) {
    list.append(i);
}
int total = QtConcurrent::blockingMappedReduced(list,
    [](const int &data) {return data * 2;},
    [](int &total, const int &result) {total += result;}
);
```
これをビルドすると、OS Xのclang++では以下のエラーになった。

```shell-session
main.cpp:29:17: error: no matching function for call to 'blockingMappedReduced'
    int total = QtConcurrent::blockingMappedReduced(list,
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../qt/Qt5.4.0/5.4/clang_64/lib/QtConcurrent.framework/Versions/5/Headers/qtconcurrentmap.h:199:12: note: candidate template ignored: couldn't infer template argument 'ResultType'
ResultType blockingMappedReduced(const Sequence &sequence,
           ^
../../qt/Qt5.4.0/5.4/clang_64/lib/QtConcurrent.framework/Versions/5/Headers/qtconcurrentmap.h:213:65: note: candidate template ignored: substitution failure [with MapFunctor = <lambda at main.cpp:30:9>, ReduceFunctor = <lambda at main.cpp:31:9>, Sequence = QList<int>]: implicit instantiation of undefined template 'QtPrivate::ReduceResultType<<lambda at main.cpp:31:9> >'
typename QtPrivate::ReduceResultType<ReduceFunctor>::ResultType blockingMappedReduced(const Sequence &sequence,
                    ~~~~~~~~~~~~~~~~                            ^
../../qt/Qt5.4.0/5.4/clang_64/lib/QtConcurrent.framework/Versions/5/Headers/qtconcurrentmap.h:228:12: note: candidate function template not viable: requires at least 4 arguments, but 3 were provided
ResultType blockingMappedReduced(Iterator begin,
           ^
../../qt/Qt5.4.0/5.4/clang_64/lib/QtConcurrent.framework/Versions/5/Headers/qtconcurrentmap.h:243:65: note: candidate function template not viable: requires at least 4 arguments, but 3 were provided
typename QtPrivate::ReduceResultType<ReduceFunctor>::ResultType blockingMappedReduced(Iterator begin,
                                                                ^
1 error generated.
```

何がいけないのかさっぱりわからない。
おそらく、`couldn't infer template argument 'ResultType'` と言うメッセージから、templateの型の推論に失敗しているものと思われる。

# うまく行く例(lambdaを使わない)
そこで、まずはwordcountの例にならって、lambdaを使わずにグローバル関数を使ってみる。

```cpp
int multi(const int &data) {
    return data * 2;
}

void reduce(int &total, const int &result) {
    total += result;
}

int total = QtConcurrent::blockingMappedReduced(list, multi, reduce);
```

これは、ビルドに成功し結果も出る。

# lambdaを変数に入れてみる
そこで、lambdaを一度autoの変数に入れてみることにする。

```cpp
auto mapFunc = [](const int &data) -> int {return data * 2;};
auto reduceFunc = [](int &total, const int &result) {total += result;};
int total = QtConcurrent::blockingMappedReduced(list, mapFunc, reduceFunc);
```
しかし、同じエラーが出た。

```shell-session
main.cpp:31:17: error: no matching function for call to 'blockingMappedReduced'
    int total = QtConcurrent::blockingMappedReduced(list, mapFunc, reduceFunc);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../qt/Qt5.4.0/5.4/clang_64/lib/QtConcurrent.framework/Versions/5/Headers/qtconcurrentmap.h:199:12: note: candidate template ignored: couldn't infer template argument 'ResultType'
ResultType blockingMappedReduced(const Sequence &sequence,
           ^
```

# autoを使わずにfunctionにしてみる。

```cpp
std::function<int (const int&)> mapFunc = [](const int &data) {return data * 2;};
std::function<void (int &, const int&)> reduceFunc = [](int &total, const int &result) {total += result;};
int total = QtConcurrent::blockingMappedReduced(list, mapFunc, reduceFunc);
```
autoを使うと推論が正しく働かないのかと思ってシグネチャがわかる形にしてみたが、やはりエラーになる。

```shell-session
main.cpp:32:17: error: no matching function for call to 'blockingMappedReduced'
    int total = QtConcurrent::blockingMappedReduced(list, mapFunc, reduceFunc);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../qt/Qt5.4.0/5.4/clang_64/lib/QtConcurrent.framework/Versions/5/Headers/qtconcurrentmap.h:199:12: note: candidate template ignored: couldn't infer template argument 'ResultType'
ResultType blockingMappedReduced(const Sequence &sequence,
           ^
```

いい加減トライ＆エラーで試すのも厳しくなってきたので、qtconcurrentmap.hにあるblockingMappedReducedの宣言を見る。

```cpp
template <typename ResultType, typename Sequence, typename MapFunctor, typename ReduceFunctor>
ResultType blockingMappedReduced(const Sequence &sequence,
                                 MapFunctor map,
                                 ReduceFunctor reduce,
                                 ReduceOptions options = ReduceOptions(UnorderedReduce | SequentialReduce))
```

```cpp
template <typename MapFunctor, typename ReduceFunctor, typename Sequence>
typename QtPrivate::ReduceResultType<ReduceFunctor>::ResultType blockingMappedReduced(const Sequence &sequence,
                                 MapFunctor map,
                                 ReduceFunctor reduce,
                                 ReduceOptions options = ReduceOptions(UnorderedReduce | SequentialReduce))
```

エラーメッセージから判断するに、最初の方の宣言が使われていそうなので、ResultTypeを教えてやることにする。

```cpp
std::function<int (const int&)> mapFunc = [](const int &data) {return data * 2;};
std::function<void (int &, const int&)> reduceFunc = [](int &total, const int &result) {total += result;};
int total = QtConcurrent::blockingMappedReduced<int>(list, mapFunc, reduceFunc);
```
これで、やっとビルドが通るようになった。
ちなみに、このやり方の場合はreduceFuncの型はautoでも大丈夫だった。

# lambdaで書きつつ一時変数に入れない
ここまでわかれば、残りのtemplate引数も全て型を教えてやれば、一時変数を使わずにlambdaを指定できる。

```cpp
int total = QtConcurrent::blockingMappedReduced<int, QList<int>, std::function<int (const int&)>, std::function<void (int &, const int&)> >(list,
    [](const int &data) {return data * 2;},
    [](int &total, const int &result) {total += result;}
);
```
std::functionを使わずに関数ポインタの型を書いても良い。(あらかじめtypedefしておいた方が見やすいと思うけど)

```cpp
int total = QtConcurrent::blockingMappedReduced<int, QList<int>, int (*)(const int&), void (*)(int &, const int&)>(list,
    [](const int &data) {return data * 2;},
    [](int &total, const int &result) {total += result;}
);
```

templateは苦手で、ずっと避けてきたのだけれど、templateがらみのエラーが出るとなかなか原因がつかめなくて本当に苦労してしまう。