読者です 読者をやめる 読者になる 読者になる

ぷろみん

プログラミング的な内容を扱ってます

C++で無理矢理クロージャを使ってみる

クロージャ

現在クロージャという表現を使う場合状態を持ったラムダを指す場合が多い気がします。 つまり、ワンライナーで書けるクラスといった感じです。これはC++では関数オブジェクト的な手法で似たようなことができますが、javascript程普通なものではありません。

// javascript
const twice = (action) => { action(); action(); };

const counter = () => {
  let count = 0;
  return () => {
    count++;
    console.log(i);
    return i;
  };  
};

const a = counter();
twice(a); // 1, 2

const b = counter();
twice(b); // 1, 2

C++だと外部ストレージを用いなければできないと思っていましたが、内部ローカル変数を参照でバインドし延命させることでできました。

// C++
auto twice = [](auto action) { action(); action(); };
    
auto counter = [] {
    int count = 0;
    auto increment = [](int& count) { count++; std::cout << count; };
    return std::bind(increment, count);
};
    
auto a = counter();
twice(a); // 1, 2
    
auto b = counter();
twice(b); // 1, 2

応用

これを利用するとfor_each中にコンテキストを保存することができます。
今回は前回のループの値を利用してみました。
素直にローカル変数をキャプチャした方が良いような気もしますが、内部で完結しているのも1つのメリットではないでしょうか。

#include <algorithm>
#include <vector>
#include <functional>
#include <iostream>

namespace {

auto context = [](auto function) {
    auto context = std::make_pair(false, 0);
    auto action = [](decltype(context)& context, auto& function, auto& value) {
        function(value, context);
    };
    return std::bind(action, context, function, std::placeholders::_1);
};

}

int main() {
    std::vector<int> vec = { 5, 10, 15, 20, 25 };
    std::for_each(vec.cbegin(), vec.cend(), context([](auto& value, auto& context) {
        if(context.first) {
            std::cout << value - context.second << std::endl;
        }
        context = std::make_pair(true, value);
    }));
}

参考

クロージャとラムダ式をザックリ解説してみる | No Creativity, No Life!