I have a mixture now of virtual inheritance and CRTP.
Basically, one super simple call root class:
// First a macro I use later
#define GRPC_NATIVE_NAME_REQUEST( name ) \
void native_name_request() { \
m_service->Request ## name ## (&m_ctx, &m_request, &m_responder, m_cq, m_cq, this); \
}
class RpcCallBase {
public:
RpcCallBase() {};
virtual ~RpcCallBase() {};
virtual void proceed() noexcept {
MOOSE_ASSERT_MSG(true, "RPC implementation does not overload proceed()");
};
};
This is what I use to cast the void ptr to in order to get it to RTTI the right type. Then, on top of this, the actual CRTP base. Like this:
// First a macro I use later
#define GRPC_NATIVE_NAME_REQUEST( name ) \
void native_name_request() { \
m_service->Request ## name ## (&m_ctx, &m_request, &m_responder, m_cq, m_cq, this); \
}
template< class DerivedType, class RequestType, class ResponseType >
class MyServiceCall : public RpcCallBase {
public:
typedef MyServiceCall<DerivedType, RequestType, ResponseType> base_type;
MyServiceCall() {
// constructor with service specific stuff such as parent object and so on
proceed(); like in the example
}
void proceed() noexcept override {
// Much like the example, except:
if (m_status == CREATE) {
m_status = PROCESS;
static_cast<DerivedType *>(this)->native_name_request();
// this is what the macro injects in order to fake the right type in here. See below.
} else if (m_status == PROCESS) {
// new object of CRTP derived type
new base_type(m_service, m_cq, m_parent);
// CRTP to the actual work, overloaded by derived class
grpc::Status retstat = static_cast<DerivedType *>(this)->work();
}
// rest of the stuff pretty much like the example except template types
}
and then, each call can be implemented nicely:
class FooMethodCall : public MyServiceCall<FooMethodCall, FooMethodRequest, FooMethodResponse> {
public:
GRPC_NATIVE_NAME_REQUEST( FooMethod ) // I know, not perfect but it does the trick
FooMethodCall(MyService::AsyncService *n_service, ServerCompletionQueue *n_cq, MyServiceImpl *n_parent)
: base_type(n_service, n_cq, n_parent) {
}
grpc::Status work() {
// do the actual work and return a status object
}
}
Finally, in the async loop it looks like this:
void MyServiceImpl::HandleRpcs() {
// Spawn a new CallData instance to serve new clients.
// The q takes ownership
new FooMethodCall(&m_service_instance, m_cq.get(), this);
new BarMethodCall(&m_service_instance, m_cq.get(), this);