- Java 8を関数型っぽく使うためのおまじない – きしだのはてな
- Java 8を関数型っぽく使うためのおまじないをC#でやってみた – ぐるぐる~
- Java 8を関数型っぽく使うためのおまじないをF#でやってみた – ぐるぐる~
C++はラムダ式もfunction型(JavaのFunction型に大体対応?)も、2011年の規格改定(通称C++11)から使えたので、似たようなことはすでにできます。上記の記事のパクリなので、上記の記事をまずは読んでから読むことをおすすめします。
Function型
C++11で標準に入ったfunction型をみてみましょう。名前空間まで含めるとstd::funcitonです。こんな感じで使います。
using namespace std; function<string (string)> enclose = [](string s) { return '[' + s + ']'; }; |
テンプレートで関数を表す型によって引数・戻り値を記述します。string (string)という型の構文は関数を表す型であり、おおむね関数プロトタイプから関数名を取り除いた感じです。
呼び出し型は普通の関数と同じです。
cout << enclose("foo") << endl; |
こうすると、次のような表示になります。
[foo]
もうひとつ関数を定義してみましょう。最初の文字を大文字にする関数です。
function<string (string)> capitalize = [](string s) { return static_cast<char>(toupper(s[0])) + boost::algorithm::to_lower_copy(s.substr(1)); }; |
0文字の文字列を与えると死にますが、コード例ってことで大目にみてください。to_lower_copyはBoost.StringAlgoライブラリの関数です。標準ライブラリのtoupper/tolower関数は、文字型(1文字)にしか使用できないので、仕方なく使用しています。
呼び出してみます。
cout << capitalize("foo") << endl; |
こんな感じの表示になります。
Foo
この2つを順に呼び出して、capitalizeしてencloseしようとすると、こんな感じになりますね。
cout << enclose(capitalize("foo")) << endl; |
表示はこうなります。
[Foo]
こういう場合、std::bindを使うと連結できます。
using namespace std::placeholders; // _1のため必要 cout << bind(capitalize, bind(enclose, _1))("foo") << endl; |
これは、関数合成として使えます。
function<string (string)> capEnc = bind(capitalize, bind(enclose, _1)); cout << capEnc("foo") << endl; |
andThen相当としてstd::bindを持ち出すのが適切なのか若干疑問ですが、C++11で関数型っぽいことができることがわかりました。やりましたね!
別名を使う?
さて、Functionって長いので、Fとか短く書きたいですね。そういう場合はCならtypedefでしたが、C++11ではusingを使います。今回のようにテンプレートが関係する場合、usingしか使えません。
template<typename T> using F = function<T>; |
余談ですが、typedefの代わりとしてusingを使えば、typedefの意味不明な構文とおさらばできてありがたいです。
もっとも、C#のvarのようなautoがあるので、こちらが便利です。
auto enclose = [](string s) { return '[' + s + ']'; }; auto capitalize = [](string s) { return static_cast<char>(toupper(s[0])) + boost::algorithm::to_lower_copy(s.substr(1)); }; auto capEnc = bind(enclose, bind(capitalize, _1)); |
なお、ラムダ式の型もstd::bindが返す型も実はstd::functionではありません。std::functionは関数呼び出しできるものなら何でも格納できるポリモーフィックな型です。そのため、通常はautoで受け取るほうが実行効率が良いです。
関数合成
C#版同様、省略します。もしやるとしたら、hoge(“foobaryah”) | middle | capitalize | encloseのように|演算子で繋げる形にするのがC++っぽいような感じがします。
カリー化
std::function引数は引数が何個でも対応しているので、素直にそれに甘えたら良いのです。が、それでは話が進まないので、元の例に対応するようにカリー化した形式を書いてみましょう。
// function<function<string(string)> (string)> auto sandwich = [](string enc) { return [=](string str) { return enc + str + enc; } }; |
[]の中に出てきた=は、外側の変数を内側で使用する指示になります。
呼び出しは次のようになります。
cout << sandwich("***")("sanded!") << endl; |
表示は次のようになります。
***sanded!***
3引数版は省略します。そういう使い方をする必要はないでしょうから。19:00頃追加:やりました。
auto encloseC = [](string pre) { return [=](string post) { return [=](string s) { return pre + s + post; }; }; }; auto encloseCurly = encloseC("{")("}"); cout << encloseCurly("||Φ|(|´|Д|`|)|Φ||") << endl; |
何より、部分適用したいだけならstd::bindを素直に使えばいいと思います。なお、bindは任意の引数について部分適用する機能もあります。
auto enclose2 = [](string left, string str, string right) { return left + str + right; }; auto encloseCurly = bind(enclose2, "{", _1, "}"); cout << encloseCurly("囲まれた!") << endl; |
まとめ
ここまでのコードをまとめて載せます。Visual C++ 2012、g++ 4.5.3 (Cygwin)、clang 3.2 svn (Apple LLVM 4.2)で動くことを確認しました。encloseC/encloseCurryの例はVisual C++だとダメでした。
C#と違い、auto(C#のvar相当)がラムダ式にも使えるのは良いです。その一方、ラムダ式の文法がC#やJavaより冗長なのが残念です。
ラムダ式の[]が気持ち悪いという声はよく聞きますが、「ないよりマシ」と自身に言い聞かせましょう。そうしているうち、私は気にならなくなりました。
#include <iostream> #include <string> #include <functional> #include <boost/algorithm/string/case_conv.hpp> int main() { using namespace std; using namespace std::placeholders; auto enclose = [](string s) { return '[' + s + ']'; }; auto capitalize = [](string s) { return static_cast<char>(toupper(s[0])) + boost::algorithm::to_lower_copy(s.substr(1)); }; auto capEnc = bind(enclose, bind(capitalize, _1)); cout << enclose("foo") << endl; cout << capitalize("foo") << endl; cout << capEnc("foo") << endl; auto enclose2 = [](string left, string str, string right) { return left + str + right; }; auto encloseCurly = bind(enclose2, "{", _1, "}"); cout << encloseCurly("囲まれた!") << endl; } |
#include <iostream> #include <string> #include <functional> int main() { using namespace std; using namespace std::placeholders; auto encloseC = [](string pre) { return [=](string post) { return [=](string s) { return pre + s + post; }; }; }; auto encloseCurly = encloseC("{")("}"); cout << encloseCurly("||Φ|(|´|Д|`|)|Φ||") << endl; } |
2013年5月5日19:00頃追記:カリー化の箇所を追加しました。
スポンサード リンク |
この記事のカテゴリ
- C++ ⇒ Java 8を関数型っぽく使うためのおまじないをC++でやってみた
「関数型っぽく」という趣旨なのに「そういう使い方をする必要はない」で3引数のカリー化を省略してしまうのは、ちょっとさびしいです・・・
確かに、このままでいるのも中途半端だと思い、カリー化の話の部分を追加しました。