Following up on yesterday's post, I worked out how to capture the return value for a continuation, that is, a future computation.
(Aside: This raised a question in my mind: what should be the behavior of asking for the result of a computation which has not yet taken place? In my prototype, I return a default constructed value (e.g., 0
for an int
). Another option might be to raise an appropriate exception as it is likely a programming error. Yet another is to provide a sentinal value or a predicate to ask the state of the computation. That last choice is interesting: if the continuation changes to a different context before finishing, the computation could be half-completed or even discarded. Questions for another day.)
The new sample main
is:
#include <cstdlib>
#include <iostream>
#include "continuations.h"
static int
increment (int id, int *n)
{
int m = *n;
std::cout << id << " thinks: " << *n << std::endl;
++*n;
return m;
}
int
main (void)
{
int initial_value = 0;
continuation<void> main_uc;
// TODO: Why not deduce from the return type of increment?
continuation<int> inc0_uc (main_uc, increment, 1, &initial_value);
continuation<int> inc1_uc (inc0_uc, increment, 2, &initial_value);
std::cout << "1 said: " << static_cast<int> (inc0_uc) << std::endl;
std::cout << "2 said: " << static_cast<int> (inc1_uc) << std::endl;
main_uc.switch_to (inc1_uc);
std::cout << "All done: " << initial_value << std::endl;
std::cout << "1 says: " << static_cast<int> (inc0_uc) << std::endl;
std::cout << "2 says: " << static_cast<int> (inc1_uc) << std::endl;
return EXIT_SUCCESS;
}
The header is somewhat hairy, however:
#ifndef _CONTINUATION_H
#define _CONTINUATION_H
#include <stdexcept>
#include <errno.h>
#include <string.h>
#include <ucontext.h>
#define STACK_SIZE 4096
template <typename R> class continuation;
class continuation_base : public ucontext_t {
char stack[STACK_SIZE];
protected:
continuation_base () {
initialize ();
}
template<typename R1>
continuation_base (continuation<R1> &next) {
initialize ();
uc_link = &next;
}
public:
template<typename R1>
int switch_to (continuation<R1> &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;
}
};
template<typename R>
void apply (R (*lambda) (), R *r) {
*r = lambda ();
}
template<typename R, typename T0>
void apply (R (*lambda) (T0), R *r, T0 t0) {
*r = lambda (t0);
}
template<typename R, typename T0, typename T1>
void apply (R (*lambda) (T0, T1), R *r, T0 t0, T1 t1) {
*r = lambda (t0, t1);
}
template<typename R>
class continuation : public continuation_base {
R value;
public:
continuation () { }
template<typename R1>
continuation (continuation<R1> &next, R (*lambda) ())
: continuation_base (next) {
void (*fp) (R (*) (), R *) = apply<R>;
makecontext (this,
reinterpret_cast<void (*) (void)> (fp),
2, lambda, &value);
}
template<typename R1, typename T0>
continuation (continuation<R1> &next, R (*lambda) (T0), T0 t0)
: continuation_base (next) {
void (*fp) (R (*) (T0), R *, T0) = apply<R, T0>;
makecontext (this,
reinterpret_cast<void (*) (void)> (fp),
3, lambda, &value, t0);
}
template<typename R1, typename T0, typename T1>
continuation (continuation<R1> &next, R (*lambda) (T0, T1),
T0 t0, T1 t1)
: continuation_base (next) {
void (*fp) (R (*) (T0, T1), R *, T0, T1) = apply<R, T0, T1>;
makecontext (this,
reinterpret_cast<void (*) (void)> (fp),
4, lambda, &value, t0, t1);
}
operator R () { return value; }
};
template<>
class continuation<void> : public continuation_base
{
public:
continuation () { }
template<typename R1>
continuation (continuation<R1> &next) : continuation_base (next) { }
};
#endif /* _CONTINUATION_H */
Two techniques of note are the specialization for void
return and moving non-return value-related code into a shared base class—a common trick when parameterizing return types—, and the use of apply
handle the return value from lambda
in the continuation.
makecontext
takes a void (*) (void)
function pointer for the program text of the ucontext_t
, which requires a cast of apply
. I found that simply:
makecontext (this,
reinterpret_cast<void (*) (void)> (&apply<int, int, int *>),
4, lambda, &value, t0, t1);
would not compile with GCC 3.3.5. This is an area of C++ where my fu is insufficient to know at sight if my code is legal—although I presume it is. So I resorted to local variable, fp
, for the cast and suddenly GCC was satisfied (see above).
So the short of it is that now I can interrogate my continuation for its computed value.