Another introduction to programming language C 13: 【番外編】C言語でクロージャを実装する

簡単なクロージャの例として,引数にnを足す関数を生成する関数をC言語で考える.

まずはお約束の#includeから.

#include <stdio.h>
#include <stdlib.h>

やりたいことはSchemeで言えば

(define (make-plus-n n) (lambda (x) (+ n x)))

なのだが,レキシカルクロージャを持たないC言語では自前で変数をラップする必要がある.そこで,こんな構造体を作ってみる.

struct make_plus_n_context_t {
  int _n;
  int (*_func)(const struct make_plus_n_context_t *, int);
};
typedef struct make_plus_n_context_t MAKE_PLUS_N_CONTEXT_T;

足し算関数の実体を用意しておく.

static int plus_n(const MAKE_PLUS_N_CONTEXT_T *context, int x) {
  return context->_n + x;
}

最後に関数make_plus_nを定義する.

MAKE_PLUS_N_CONTEXT_T *make_plus_n(int n) {
  MAKE_PLUS_N_CONTEXT_T *context;
  context = (MAKE_PLUS_N_CONTEXT_T *)malloc(sizeof(MAKE_PLUS_N_CONTEXT_T));
  context->_n = n;
  context->_func = plus_n;
  return context;
}

この関数は,関数を返す代わりにmake_plus_n_context_t構造体を(メモリを新たに確保して)返す.この構造体から_funcを呼んでやるのは,次のようなマクロを用意すると便利である.

#define FUNC_CALL(context, param) ((context)->_func((context), (param)))

次のように使う.

int main(void) {
  MAKE_PLUS_N_CONTEXT_T *plus_2 = make_plus_n(2);
  int y = FUNC_CALL(plus_2, 1);
  printf("%d\n", y);
  free(plus_2);
  return 0;
}

Cウィザードはこのようなことは朝飯前にやってしまう.(いや,もっといいコードを書くだろう.)

僕は上記のようなコードを一瞬で書くことはできないので,Cウィザードではないのだろう.ただし,C++版の次のコードならすらすらと出てくる.(標準テンプレートライブラリがstd::plusを用意してくれたおかげであるが.)

template <typename T> std::binder1st< std::plus<T> > make_plus_n(T n) {
  return std::binder1st< std::plus<T> >(std::plus<T>(), n);
}
std::binder1st< std::plus<int> > plus_2 = make_plus_n(2);
std::cout << plus_2(3) << std::endl;

Scheme版の

(define (make-plus-n n) (lambda (x) (+ n x)))

ならもっとたやすく書ける.
Comments