Code for both static and dynamic polymorphism in C++?

118 Views Asked by At

Is there a way so that I can write code as the follows:

IConnection<Socket> s{Socket{...}}; // statically
Connection* c = new Socket{...};          //dynamically

// a heterogeneous list that require dynamically dispatch /existential type/ type erase
List<Connection*> l { new Socket{...}
, new MockSock{...}, new LocalDataBase{...}, ...} 

I tried to implement class suitable for both dynamic and static polymorphism on a need-to-erase basis, i.e. only use dynamic polymorphism if i have to (heterogeneous list, e.g.). I write following code

class Connection {
public:
Connection& operator=(Connection& c) = delete;
Connection(Connection& c) = delete;
Connection& operator=(Connection&& c) = delete;
Connection(Connection&& c) = delete;
virtual ~Connection() = 0;
virtual string Recv() = 0;
}

template<typename T>
class IConnection : Connection{
public:
inline T* dispatch(){
   return static_cast<T*>(this);
}
virtual string Recv() override{
    return dispatch()->Recv();
}
...
}

However, if i am to implement class Socket :IConnection<Socket>, then Recv() in class Socket would still be a dynamic dispatch.

1

There are 1 best solutions below

7
shaked cohen On

Your problem starts when you inherit from Connection. You defined Connection as an abstract class, meaning the compiler will generate a vtable and all those fun toys.
Things that expect a Connection* expect the vtable and such.
But at the same time, you want the Connection interface to sometimes be static. Which means the callers will have to change the way they call stuff. But those are two very distinct types with two very different calling conventions.
You can't have both under the same interface.\

What you can do is a templated Connection class that receives a type and assumes that that type has the functions you expect and calls them in functions with the same name. You won't be able to pass it to stuff that expects Connection*generically, but it might still suit your needs.

template <typename T>
class Connection {
public:
    Connection(T&& impl) : _impl(impl) {}
    std::string recv() { return _impl.recv(); }
    ... rest of your implementation...
private:
    T impl;
};

This way, if foo is an object that implements some interface that requires recv(), Connection<foo> will use the vtable and you'll have dynamic dispatch. If you construct a Connection<bar> where bar is a plain class that happens to have a recv() method, it will still work, and you'll get static dispatch.