大体一年ぐらい前に
という記事を書きました。
これをC++14で、パフォーマンスを気にしないブルジョワ仕様で作ってみてmapで力尽きたという報告です。
map関数本体
複数コンテナへの対応は難しいので、今回はstd::vectorのみを考えた。
#include <algorithm> #include <vector> #include <iterator> template <class T, class R> auto map(const std::vector<T> a, std::function<R(T)> fn) { std::vector<R> result = {}; std::transform(a.begin(), a.end(), std::back_inserter(result), fn); return result; }
メモリがどうとかはさっぱり考えてないので、resultあたりがおかしくなる気がしないでもないですが、とりあえず動く。
まあ、ここまで書くともうstd::transformをそのまま使えばいいじゃんという話ですが、忌むべき副作用はできるだけ減らしたい。モチベーションはただそれだけです。
使い方
int main() { std::function<std::string(int)> twice_show = [](int x) { return std::to_string(x * 2); }; // 絶望ポイント std::vector<int> v = { 3, 5, 6}; auto result = map(v, twice_show); std::for_each(result.begin(), result.end(), [](const std::string& s) { std::cout << s << std::endl; }); }
残念ながらこの実装ではテンプレートにラムダ式がマッチできないらしく、ラムダ式をそのままmap関数へ渡すことができない。
そのため、一度ラムダ式をstd::function型の変数に代入する処理が必要になる。
これではあまり意味がない…
解決策 その1
関数のオーバーロードを悪用してみた。
次のコードをmap関数の下に追加し、ディスパッチみたいな処理をさせる。
template <class T, class F> auto map(const std::vector<T> a, F fn) { std::function<decltype(fn(a[0]))(T)> g = fn; return map(a, g); }
decltypeサイコー!!! 要するに型推論を行うオーバーロード関数を自前で用意する、そういうことです。 しかし、map関数本体がこのディスパッチ関数の前に宣言されている必要があるので、あとから違うコンテナでmap関数を定義するとコンパイルできない。
解決策 その2
返り値の型をdecltypeで求めても良いはず。
map関数を次のように書き換えます。
template <class T, class F> auto map(const std::vector<T> a, const F fn) -> decltype(std::vector<decltype(fn(a[0]))>()) // なんだこれ { std::vector<decltype( fn(a[0]) )> result = {}; std::transform(a.begin(), a.end(), std::back_inserter(result), fn); return result; }
これでよし。
まったく良くないのでdecltype(auto)
どう見てもdecltype(std::vector<decltype(fn(a[0])>())
は読みにくい。
そこで、decltype(auto)を使う。
template <class T, class F> decltype(auto) map(const std::vector<T> a, const F fn) { std::vector<decltype( fn(a[0]) )> result = {}; std::transform(a.cbegin(), a.cend(), std::back_inserter(result), fn); return result; }
あらスッキリ!
できたコード
参考サイト
追記
テンプレートテンプレート使えばfmap作れそう。ということで実験中。