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頃追記:カリー化の箇所を追加しました。


スポンサード リンク

この記事のカテゴリ

  • ⇒ Java 8を関数型っぽく使うためのおまじないをC++でやってみた