Skip navigation

A continuation is an archive of a program’s state. Objects, coroutines (not actually present in C or C++), save files, and even core dumps all count as continuations. However, in most cases, the word Continuation refers to something along the lines of an archive of part or all of a stack, which can be restored at a later time, and execution continued from where it was when the continuation was created. In C, continuations are conceptually easy (though rather dangerous) to create. You simply create a virtual stack:

struct continuation

{

void *archive;

unsigned size, offset;

jmpbuf jb;

};

struct continuation_link

{

continuation *c;

jmpbuf ret;

continuation_link *next;

};

Physically copy the portion of the stack that you want to keep, and hope that the program doesn’t explode when you load it back in. Be warned that this is ridiculously dangerous in the best of times.

AND DON’T YOU DARE TRY IT WITH C++!

Yes, I know, C++ is supposed to be a almost-strict super-set of C. It’s that super-set bit that gets you, specifically exceptions. Templates and classes usually won’t mean diddly to the version of continuations I scribbled out above, but there’s no real way to know how exceptions have been implemented. As long as you don’t actually try to throw anything, you might be fine. Exceptions implemented by fiddling around with return pointers might (maybe) work right if you catch them soon enough, exceptions implemented as arguments or a virtual stack hopefully won’t explode in your face unless used, but the entire thing’s a massive shot in the dark, and it’s honestly not worth the risk.

If there’s a standard way in C++, it would have to be the following:

class function

{

public:

continuation* demo_function( continuation *c )

{

if( c == 0 )

{

demo_function_1();

return( new continuation( /* Stuff here. */ ) );

}

switch( c->switch_control )

{

case 0:

demo_function_2();

c->increment();

return( c );

case 1:

demo_function_3();

delete c;

}

return( 0 );

};

private:

void demo_function_1() {};

void demo_function_2() {};

void demo_function_3() {};

};

To be honest, though, that method’s inconvientient and somewhat annoying. But there is another way. Just be careful who you show it to.

The way I recommend implementing continuations involves exceptions. Yes, the bit that causes problems with the first method works to implement this version. The first step is to define the type we’re going to be throwing:

class continuation

{

public:

class type

{

public:

template< typename T >

static unsigned type_key()

{

static id = 0;

if( id == 0 )

{

id = unused_key;

unused_key += 1;

}

return( id );

};

virtual unsigned get_type_key()

{

return( type_key< type >() );

};

virtual void die()

{

delete this;

};

private:

static unsigned unused_key;

};

template< typename T >

class subtype : public type

{

public:

T var;

virtual unsigned get_type_key()

{

return( type_key< subtype >() );

};

virtual void die()

{

delete this;

};

};

list< type* > vstack;

template< typename T >

void push( T &that )

{

vstack.push_back( new subtype< T >()  );

vstack.back()->var = that;

};

template< typename T >

void pop( T &that )

{

if( vstack.back()->get_type_key() != type::type_key< T >() )

{

throw( T() );

}

that = vstack.back()->var;

vstack.back()->die();

vstack.pop_back();

};

};

/* We initialize it to 1 instead of 0 because it makes everything work. */

continuation::type::unused_key = 1;

Before I continue, I should probably explain a few things:

1) I never want to look up the syntax for overloading delete, hence virtual void die();

2) I didn’t want to spend time writing an exception class for an example, so pop throws T instead of something informative.

A proper implementation should at least provide some useful sort of feedback as to what the type of subtype::var is, as well as provide a destructor to clean up vstack, and watch for failed allocations in push.

Once you have the continuation class, you need to start thinking about how to actually use it. A good way is as follows:

void demo_function( continuation *c )

{

unsigned a = 0, b = 0;

if( c != 0 )

{

c->pop( b );

c->pop( a );

}

switch( a )

{

case 0:

try

{

a = 1;

yield();

}

catch( continuation *c2  )

{

c2->push( a );

c2->push( b );

throw();

}

break;

case 1:

try

{

a += b;

b = 1;

yield();

}

catch( continuation *c2 )

{

c2->push( a );

c2->push( b );

throw;

}

break;

case 3:

};

void yield()

{

throw( new continuation );

}

But, to be honest, this isn’t my preferred way. There are some packages that use this approach, such as the Protothreads package for C, which uses it to emulate threads, but you can’t properly embed switches in it without a bit more work. My preference is to use a function with multiple entrance points, like this:

void demo_function_primary( continuation *c ),

alternate_entrance demo_function_2,

alternate_entrance demo_function_3

{

unsigned a = 0, b = 0;

try

{

yield();

}

catch( continuation *c2 )

{

c2->push( a );

c2->push( b );

c2->push( &demo_function_2 );

throw;

}

entrance demo_function_2:

void (next*)( continuation* );

try{

c2->pop( next );

}

catch( void (*)( continuation* ) )

{

/* No need to deal with this error. */

}

c2->pop( b );

c2->pop( a );

try

{

yield();

}

catch( continuation *c2 )

{

c2->push( a );

c2->push( b );

c2->push( &demo_function_3 );

throw;

}

entrance demo_function_3:

/* Etc. */

}

Note that C and C++ don’t support multiple entrances per function, so you can’t actually do that. You can emulate it with a modified version of the switch implementation, though you might not want to:

void demo_function( continuation *c )

{

unsigned a = 0, b = 0, index = 0;

if( c != 0 )

{

c->pop( index );

c->pop( b );

c->pop( a );

}

switch( index )

{

case 1:

goto middle;

case 2:

goto end;

default:

}

try

{

/* Yada. */

}

catch( continuation *c2 )

{

/* Yada. */

}

middle:

/* Yada. */

try

{

/* Yada. */

}

catch( continuation *c2 )

{

/* Yada. */

}

end:

}

Me? I’m holding out for multiple entrances. I don’t think they’ll be adding them to the C++ standard any time soon (if ever), but sometimes the alternative just isn’t good practice. Besides which, who remembers the details for using goto anyways?

Follow

Get every new post delivered to your Inbox.