Wednesday, September 14, 2005

Continuations in C++, a starting point

Starting from the example in the GNU C Library info pages on System V contexts, I cooked up a starting point for continuations in C++. Usage is simple:

#include <stdio.h>

#include "continuation.h"

static void
increment (int *n)
{
  printf ("Nothing interesting: %d\n", *n);
  ++*n;
}

int
main (void)
{
  int initial_value = 0;

  continuation main_uc;
  continuation inc0_uc (main_uc, increment, &initial_value);
  continuation inc1_uc (inc0_uc, increment, &initial_value);

  main_uc.switch_to (inc1_uc);

  printf ("I'm back: %d\n", initial_value);

  return 0;
}

Program output is as expected:

Nothing interesting: 0
Nothing interesting: 1
I'm back: 2

That is, the first frame runs increment; the second frame does the same; and the last frame runs main from where it left off after jumping to the first frame: a continuation.

The header:

#ifndef _CONTINUATION_H
#define _CONTINUATION_H

#include <stdexcept>

#include <errno.h>
#include <string.h>
#include <ucontext.h>

// Tricky to work out without cpu-os-specific information:
#define STACK_SIZE 4096

class continuation : public ucontext_t
{
  char stack[STACK_SIZE];

public:
  continuation () {
    initialize ();
  }

  continuation (continuation &next) {
    initialize ();
    uc_link = &next;
  }

  continuation (continuation &next, void (*lambda) (void)) {
    initialize ();
    uc_link = &next;
    set_lambda (lambda);
  }

  template<typename T0>
  continuation (continuation &next, void (*lambda) (T0), T0 t0) {
    initialize ();
    uc_link = &next;
    set_lambda (lambda, t0);
  }

  int switch_to (continuation &next) {
    return swapcontext (this, &next);
  }

private:
  void initialize () {
    if (getcontext (this) < 0)
      throw std::runtime_error (strerror (errno));

    uc_stack.ss_sp = stack;
    uc_stack.ss_size = sizeof stack;
  }

  void set_lambda (void (*lambda) ()) {
    makecontext (this, lambda, 0);
  }

  template<typename T0>
  void set_lambda (void (*lambda) (T0), T0 t0) {
    makecontext (this, (void (*) (void)) (lambda), 1, t0);
  }
};

#endif /* _CONTINUATION_H */

No comments: