C++の遅延評価と部分適用 – それと遅延評価式と部分適用式の実現方法


C++には遅延評価を実現するための機能は存在しませんが、ラムダ式やbind関数を用いることで、処理の実行を任意のタイミングに先延ばすことが可能になり、遅延評価に近い挙動を実現することが可能になります。

本記事では両者による遅延評価と部分適用の実現方法や、注意点について解説します。後半ではより利便性の高い部分適用式・遅延評価式の定義方法を紹介します。

スポンサーリンク

ラムダ式による遅延評価の実現

実引数の値評価と関数呼び出しの遅延評価を実現したい場合にはラムダ式の利用が適切です。なお厳密な遅延評価ではないため、評価時には明示的な関数呼び出しが必要となります(f())。ラムダ式は[]() { 式 }という形式で利用します。

auto f = []() {
   printf("hello");
};

f(); // "hello"

このように、処理printf("hello")は、ラムダ式のオブジェクトfに対する関数呼び出しのタイミングで実行される事になります。

以下の例でも同様です。関数オブジェクトgの呼び出しと、文字列オブジェクトstd::string("99")の生成はf関数呼出し時に行われます。

auto g = [](auto v) {
   return std::stoi(v);
};
auto f = [g] {
   return g(std::string("99"));
};

assert( 99 == f() );

このように、ラムダ式によって文字列オブジェクトの生成を関数呼出しの直前まで先延ばしすることが可能になります。なお評価値のキャッシュはされないため注意してください。

bind関数による部分適用の実現

C++標準ライブラリのstd::bindを用いることで、部分適用を実現することが可能となります。

auto f = std::bind(printf, "hello");
f(); // "hello"
     // 関数呼び出し`printf("hello")`が行われる

なお、bind関数の場合は実引数の評価が値束縛時に行われてしまうため、遅延評価を実現する機能としては不完全な点に注意が必要です。

auto f = std::bind(g, std::string("99")); // `std::string("99")`の評価値が束縛される
assert( 99 == f() );
assert( 99 == f() ); // 毎回`g`関数が呼び出される

std::bind関数に関するより詳しい解説については以下のページが参考になります。

std::bind – 引数の束縛と部分適用

部分適用式・遅延評価式の実現

bind関数はプレースホルダの指定が必要な点や、可変長引数に対応していない等の問題があります。ここではより使い勝手の良い部分適用関数の実現方法を紹介します。これによってbind関数では実現できなかった可変長引数の扱いも可能となります。また簡易的な遅延評価式としての利用も可能です。

auto f = $(puts, "hello");
f(); // "hello"

auto g = $(printf, "%s%d");
g("Shop", 99);   // "Shop 99"
g("DAISO", 100); // "DAISO 100"
ドルマークの利用に関する注意

このように$()という簡潔な記法でthunk(サンク)の生成を実現します。具体的な定義方法は次の通りです。

struct {
   template<class F, class... A> auto operator()(F&& f, A&&... a) {
      return [&](auto&&... b) {
         return std::forward<F>(f)(std::forward<A>(a)..., std::forward<decltype(b)>(b)...);
      };
   }
} $, lazy;

関数呼び出し式を任意のタイミングで呼び出すことが可能になります。

auto hello = $(puts, "hello world");
hello(); // `puts("hello world")`呼び出しが行われる
auto set = std::make_tuple(
   lazy (puts, "Shop"),
   lazy (putchar, '9')
);
auto Q = std::get<1>(set);
std::get<0>(set)(); // "Shop"
Q(), Q();           // "99"

部分適用式/部分適用関数としての利用も可能です。

auto print = $(printf, "%s%d", "C++");
print(11); // "C++11"
print(14); // "C++14"
auto p = $(printf, "%s %d");
auto q = $(p, "Shop");
q(99); // "Shop 99"
$($($(printf, "%s %d"), "Shop"), 99)(); // "Shop 99"
$($, $($($(printf, "%c%d"), 'C'), 99))()(); // "C99"

Coming Soon

C++17で利用可能になる予定のテクニックです。

//    THIS YEAR
//   Coming Soon
//  Aug  24, 2017

template<class T> struct print {
   T v; // print(T v) : v(v) {}
   void operator()() const { std::cout << v << std::endl; }
};
template<class T> print(T t) -> print<T>; // deduction guide 

auto f = print{99}; // テンプレート実引数推定
f(); // "99"
template<class F, class... A> struct lazy {
   F f; std::tuple<A...> a; lazy(F f, A... a) : f(f), a(a...) {}
   auto operator()() const { return std::apply(f, a); }
};

auto f = lazy { puts, "hello" };
f(); // "hello"

広告

関連するオススメの記事