Is a templated, compile time version of virtual base class as interface possible?

83 views Asked by At

I'm working on a framework for Model Based Systems Engineering (MBSE). In this, I have a construct called Bridge. A Bridge can send Signals (asynch calls) and/or Operations (synch calls) from one end of the Bridge to the other. Signals & Operations may also have a pay load, as defined by the frame work's end user (another C++-programmer).

The Bridge is networked so that the Brigde's input is connected to the Bridge's output via a network socket. For this to work, Signals and/or Operations (SigOps for short) as well as possible pay load need to be serialized.

A Bridge has an outgoing queue that buffers and preserve the sequence of SigOp as they ar issued. Consequently, this queue stores SigOps using generic SigOp pointers. But, it is still necessary to use a SigOp-type specific serializer.

The usual way of doing this is to use a virtual base class, that defines the interface, and then let the derived class add the implenetation. Something along the line of:

// A Bridge-type specific SigOp virtual base class/interface
template <class tBridgeDeclType>
class SigOpT
   {
   public:
      virtual void initSerialization(SigOpSerDes &fSerializer, SerializationBuffer &fSerializationBuffer) = 0;
      virtual void serializePayLoad(SigOpSerDes &fSigOpSerializer) = 0;
   };

template <class tBridgeDeclType>
class MySigOp : public  SigOpT<tBridgeDeclType>
   {
    void initSerialization(SigOpSerDes &fSerializer, SerializationBuffer &fSerializationBuffer) override final
        {
        // My implementation
        ...
        }
    
    void serializePayLoad(SigOpSerDes &fSigOpSerializer) overried final
        {
        // My serialization code specific for MySigOp<tBridgeDeclType>
        ...
        }
   };

To the question: Is there a way to do the same at compile time, thereby eliminating virtual functions, vtables, etc?

CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) does not work since that will remove a generic base class/interface type that can be stored in the Bridge queue.

template <tDerived>
class Base
   {
   }

class Derived : Base<Derived>
   {
   }

I have also looked into std::variant as a way to store different SigOp types in a queue but I'm currently locked into C++-14 (std::variant showed up in C++-17). In addition, a std::variant that can store all SigOp types of a Bridge, as well as their possible pay loads will take a lot of memory (unless I'm misstaken).

Currently, I have the pieces but I have found no way of gluing it all together. A Bridge template:

template <class tBridgeDeclType>
class BridgeT
    {

    // Enqueue Signal for subsequent serialization and transportation over network by ZMQ
    template <class tSignalType>
    inline static void post(const tSignalType &fSignal)
        {

        /**
         * Here, I know all types needed to fully define how a serialization shall be done.
         * 
         * Thus:
         * 
         *  tBridgeDeclType - The bridge declaration type. This type stores 
         *     the relations to the types below.
         *  
         *  typename tBridgeDeclType::SigOpTypes - A std::tuple<> listing all SigOp types
         *     supported by the bridge
         *  
         *  tSignalType - The specific type of SigOp (in this case a Signal) that we want
         *     to enqueue in the bridges outgoing SigOp queue (see below).
         *  
         * This setup does, in the background, provides type safety so that only SigOps 
         * explicit supported by the particular bridge type can be posted and transmitted
         * by the bridge. Using unsupported SigOps generate compilation error.  
         */
            
            
        // How to combine this with a SigOp-type specific 
        // SerializableSigOpT<tBridgeDeclType, tSigOpType> still being able to use a
        // Bridge-generic SigOp queue slot type?    

        // Enqueue SigOp
        queue.push(fSignal);
        }
    
    static std::queue<SigOpT<tBridgeDeclType>> queue;
    };

And a SigOp serializer template that redirect to the right serialization code at compile time:

template <class tBridgeDeclType, class tSigOpType>
class SerializableSigOpT : public SigOpT<tBridgeDeclType>
    {
    public:

        inline static void initSerialization(SigOpSerDes &fSigOpSerializer, const SigOpSeqNumType fSeqNumber, SerializationBuffer &fSerBuffer)
            {
            fSigOpSerializer.initSerialization(getSigOpTypeIdT<typename tBridgeDeclType::SigOpTypes, tSigOpType>::cId, fSeqNumber, fSerBuffer);
            }

        inline static void serializePayLoad(const tSigOpType &fSigOp, SigOpSerDes &fSigOpSerializer)
            {

            // Serialize pay load when such exists. This is a conditional template that compiles to no code when SigOp has no pay load.
            PayLoadSerDesT<tBridgeDeclType, tSigOpType, tSigOpType::hasPayLoad()>::serializePayLoad(fSigOp, fSigOpSerializer);
            }
    };

Is it possible to do this using C++ templates or is it one of those things that seem so straight forward on the surface but when you look into the details the devil shows up???

/Nils

0

There are 0 answers