前回、QtConcurrentのmapを取り上げたので、mappedReducedも試してみようと思ったら、すごくはまったと言う話。
ドキュメントを見てもさっぱり使い方がわからないので、Examplesディレクトリを探してみたら、qtconcurrent/wordcount と言うのがmappedReduced()のサンプルになっているらしい。
map-reduceで良く出てくるwordcoundの例になっている。
簡単のために、今回もblocking版を使うことにして、マニュアルによると以下のシグネチャになっている。(iterator版もあるけどここでは割愛)
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を使ってコードにすると以下のような感じ。
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++では以下のエラーになった。
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を使わずにグローバル関数を使ってみる。
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の変数に入れてみることにする。
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);
しかし、同じエラーが出た。
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にしてみる。
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を使うと推論が正しく働かないのかと思ってシグネチャがわかる形にしてみたが、やはりエラーになる。
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の宣言を見る。
template <typename ResultType, typename Sequence, typename MapFunctor, typename ReduceFunctor>
ResultType blockingMappedReduced(const Sequence &sequence,
MapFunctor map,
ReduceFunctor reduce,
ReduceOptions options = ReduceOptions(UnorderedReduce | SequentialReduce))
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を教えてやることにする。
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を指定できる。
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しておいた方が見やすいと思うけど)
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がらみのエラーが出るとなかなか原因がつかめなくて本当に苦労してしまう。