I think we could get away with type inference (although more work). I didn’t think this 100% through but I think this would be possible (and I apologize for the long post in advance, but this contains generated code):
Let’s say we have the following code:
ACTOR Future<int> foo() {
Future<int> f = asyncCalculation();
int count = wait(f);
return count + 1;
}
This would currently generate to this:
namespace {
// This generated class is to be used only via foo()
template <class FooActor>
class FooActorState {
public:
FooActorState()
{}
int a_body1(int loopDepth=0)
{
try {
Future<int> f = asyncCalculation();
if (static_cast<FooActor*>(this)->actor_wait_state < 0) return a_body1Catch1(actor_cancelled(), loopDepth);
Future<int> __when_expr_0 = f;
if (__when_expr_0.isReady()) { if (__when_expr_0.isError()) return a_body1Catch1(__when_expr_0.getError(), loopDepth); else return a_body1when1(__when_expr_0.get(), loopDepth); };
static_cast<FooActor*>(this)->actor_wait_state = 1;
__when_expr_0.addCallbackAndClear(static_cast<ActorCallback< FooActor, 0, int >*>(static_cast<FooActor*>(this)));
loopDepth = 0;
}
catch (Error& error) {
loopDepth = a_body1Catch1(error, loopDepth);
} catch (...) {
loopDepth = a_body1Catch1(unknown_error(), loopDepth);
}
return loopDepth;
}
int a_body1Catch1(Error error,int loopDepth=0)
{
this->~FooActorState();
static_cast<FooActor*>(this)->sendErrorAndDelPromiseRef(error);
loopDepth = 0;
return loopDepth;
}
int a_body1cont1(int const& count,int loopDepth)
{
if (!static_cast<FooActor*>(this)->SAV<int>::futures) { count + 1; this->~FooActorState(); static_cast<FooActor*>(this)->destroy(); return 0; }
new (&static_cast<FooActor*>(this)->SAV< int >::value()) int(count + 1);
this->~FooActorState();
static_cast<FooActor*>(this)->finishSendAndDelPromiseRef();
return 0;
return loopDepth;
}
int a_body1when1(int const& count,int loopDepth)
{
loopDepth = a_body1cont1(count, loopDepth);
return loopDepth;
}
void a_exitChoose1()
{
if (static_cast<FooActor*>(this)->actor_wait_state > 0) static_cast<FooActor*>(this)->actor_wait_state = 0;
static_cast<FooActor*>(this)->ActorCallback< FooActor, 0, int >::remove();
}
void a_callback_fire(ActorCallback< FooActor, 0, int >*,int value)
{
a_exitChoose1();
try {
a_body1when1(value, 0);
}
catch (Error& error) {
a_body1Catch1(error, 0);
} catch (...) {
a_body1Catch1(unknown_error(), 0);
}
}
void a_callback_error(ActorCallback< FooActor, 0, int >*,Error err)
{
a_exitChoose1();
try {
a_body1Catch1(err, 0);
}
catch (Error& error) {
a_body1Catch1(error, 0);
} catch (...) {
a_body1Catch1(unknown_error(), 0);
}
}
};
// This generated class is to be used only via foo()
class FooActor : public Actor<int>, public ActorCallback< FooActor, 0, int >, public FastAllocated<FooActor>, public FooActorState<FooActor> {
public:
using FastAllocated<FooActor>::operator new;
using FastAllocated<FooActor>::operator delete;
virtual void destroy() { this->Actor<int>::~Actor(); operator delete(this); }
friend struct ActorCallback< FooActor, 0, int >;
FooActor()
: Actor<int>(),
FooActorState<FooActor>()
{
this->a_body1();
}
void cancel()
{
auto wait_state = this->actor_wait_state;
this->actor_wait_state = -1;
switch (wait_state) {
case 1: this->a_callback_error((ActorCallback< FooActor, 0, int >*)0, actor_cancelled()); break;
}
}
};
}
Future<int> foo( ) {
return Future<int>(new FooActor());
}
Now instead of this, I would love to write this:
ACTOR Future<int> foo() {
auto f = asyncCalculation();
auto count = wait(f);
return count + 1;
}
But this is problematic because the code above now heavily depends on type inference.
However, I think it would be feasable to generate this code:
namespace {
// This generated class is to be used only via foo()
template <class FooActor>
class FooActorState {
public:
FooActorState()
{}
int a_body1(int loopDepth=0)
{
try {
auto asyncCalculation();
if (static_cast<FooActor*>(this)->actor_wait_state < 0) return a_body1Catch1(actor_cancelled(), loopDepth);
auto __when_expr_0 = f;
if (__when_expr_0.isReady()) { if (__when_expr_0.isError()) return a_body1Catch1(__when_expr_0.getError(), loopDepth); else return a_body1when1(__when_expr_0.get(), loopDepth); };
static_cast<FooActor*>(this)->actor_wait_state = 1;
__when_expr_0.addCallbackAndClear(static_cast<ActorCallback< FooActor, 0, decltype(__when_expr_0.get()) >*>(static_cast<FooActor*>(this)));
loopDepth = 0;
}
catch (Error& error) {
loopDepth = a_body1Catch1(error, loopDepth);
} catch (...) {
loopDepth = a_body1Catch1(unknown_error(), loopDepth);
}
return loopDepth;
}
int a_body1Catch1(Error error,int loopDepth=0)
{
this->~FooActorState();
static_cast<FooActor*>(this)->sendErrorAndDelPromiseRef(error);
loopDepth = 0;
return loopDepth;
}
template<class T>
int a_body1cont1(T const& count,int loopDepth)
{
if (!static_cast<FooActor*>(this)->SAV<int>::futures) { count + 1; this->~FooActorState(); static_cast<FooActor*>(this)->destroy(); return 0; }
new (&static_cast<FooActor*>(this)->SAV< int >::value()) int(count + 1);
this->~FooActorState();
static_cast<FooActor*>(this)->finishSendAndDelPromiseRef();
return 0;
return loopDepth;
}
template<class T>
int a_body1when1(T const& count,int loopDepth)
{
loopDepth = a_body1cont1(count, loopDepth);
return loopDepth;
}
void a_exitChoose1()
{
if (static_cast<FooActor*>(this)->actor_wait_state > 0) static_cast<FooActor*>(this)->actor_wait_state = 0;
static_cast<FooActor*>(this)->ActorCallback< FooActor, 0, int >::remove();
}
template<class T>
void a_callback_fire(ActorCallback< FooActor, 0>*,T value)
{
a_exitChoose1();
try {
a_body1when1(value, 0);
}
catch (Error& error) {
a_body1Catch1(error, 0);
} catch (...) {
a_body1Catch1(unknown_error(), 0);
}
}
template<class T>
void a_callback_error(ActorCallback< FooActor, 0, T >*,Error err)
{
a_exitChoose1();
try {
a_body1Catch1(err, 0);
}
catch (Error& error) {
a_body1Catch1(error, 0);
} catch (...) {
a_body1Catch1(unknown_error(), 0);
}
}
};
// This generated class is to be used only via foo()
class FooActor : public Actor<int>, public GenericActorCallback< FooActor, 0>, public FastAllocated<FooActor>, public FooActorState<FooActor> {
public:
using FastAllocated<FooActor>::operator new;
using FastAllocated<FooActor>::operator delete;
virtual void destroy() { this->Actor<int>::~Actor(); operator delete(this); }
friend struct ActorCallback< FooActor, 0, int >;
FooActor()
: Actor<int>(),
FooActorState<FooActor>()
{
this->a_body1();
}
void cancel()
{
auto wait_state = this->actor_wait_state;
this->actor_wait_state = -1;
switch (wait_state) {
case 1: this->a_callback_error((ActorCallback< FooActor, 0, int >*)0, actor_cancelled()); break;
}
}
};
}
Future<int> foo( ) {
return Future<int>(new FooActor());
}
GenericActorCallback would be something of the form:
template <class ActorType, int CallbackNumber>
struct ActorCallback : Callback
{
template<class T>
void fire(T const& value) override
{
static_cast<ActorType*>(this)->a_callback_fire(this, value);
}
void error(Error e) override
{
static_cast<ActorType*>(this)->a_callback_error(this, e);
}
};
Note that Callback does not have a template argument anymore. But I honestly don’t believe that the template argument is needed anyways - the only change necessary is to template the fire method:
struct Callback
{
Callback *prev, *next;
template<class T>
virtual void fire(T const&) {}
virtual void error(Error) {}
virtual void unwait() {}
void insert(Callback* into)
{
// Add this (uninitialized) callback just after `into`
this->prev = into;
this->next = into->next;
into->next->prev = this;
into->next = this;
}
void insertBack(Callback* into)
{
// Add this (uninitialized) callback just before `into`
this->next = into;
this->prev = into->prev;
into->prev->next = this;
into->prev = this;
}
void insertChain(Callback* into)
{
// Combine this callback's (initialized) chain and `into`'s such that
// this callback is just after `into`
auto p = this->prev;
auto n = into->next;
this->prev = into;
into->next = this;
p->next = n;
n->prev = p;
}
void remove()
{
// Remove this callback from the list it is in, and call unwait() on the
// head of that list if this was the last callback
next->prev = prev;
prev->next = next;
if (prev == next)
next->unwait();
}
int countCallbacks()
{
int count = 0;
for (Callback* c = next; c != this; c = c->next)
count++;
return count;
}
};
Now having a virtual templated function looks wrong here - but it isn’t. Virtual is all about runtime, template is static. The compiler can instantiate the fire method of all children, so it will compile.
I think the biggest drawback of this approach is that compiler errors will probably be quite hard to understand. Apart from that, I don’t see any obvious reasons why it should not work, but of course I might be missing something.