Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing Stream<T> which can be converted to Stream<U> where U is base of T

I'm trying to implement a generic input stream of objects. That is, an interface or a lightweight proxy to an implementation. Details of implementation are unknown, i.e. a user of my library can write its own stream of, say, protobuf messages, pass it to my library and get back, say, stream of strings or any other stream. I'd like to keep the interface of stream generic so that users can write their own transformations and build transformation pipelines.

The interface of a stream should look like this:

template <typename T>
class Stream {
public:
    T* input();
}

On each call, input() shall return a next object in the stream or a null pointer, if the stream is empty.

The problem is that I'd like Stream<T> to be convertible to Stream<U> if T* is convertible to U*.

My unsuccessful attempt was to use pointer to implementation like this:

class StreamImplBase {
public:
    virtual void* input_raw() = 0;
}

template <typename T>
class StreamImpl: public StreamImplBase {
public:
    void* input_raw() final { return input(); }
    virtual T* input() = 0;
}

template <typename T>
class Stream {
    StreamImplBase* impl;
public:
    Stream(StreamImpl<T>* impl): impl(impl) {}
    T* input() { return static_cast<T*>(impl->input_raw()); }
}

The constructor from StreamImpl<T> guarantees that the void* returned from input_raw() was acquired by casting T to void*, therefore static_cast<T*> is safe.

However, if I perform any conversion, this statement will not be true. That is, building Stream<T> from StreamImpl<U> is unsafe even if U* is convertible to T*.

So my question is, how do I handle this problem?

I see the next possibilities:

  • store a converter (e.g. std::function<T*(void*)>) in the stream and update it on every cast. This seems unnecessarily expensive;

  • store the result of static_cast<U*>((T*)0) and add this result to the pointer obtained from input_raw(). This seems unnecessarily dangerous;

  • add the second template parameter OrigT and store StreamImpl<OrigT>* instead of storing StreamImplBase*. This will limit possible applications of the class, which I'd like to avoid;

  • using dynamic_cast is not an option because one can't dynamic_cast from void*.

Are there any other possibilities? How do others implement something like this?


Here's a usecase. Suppose we have a protobuf message X. I'd like this to work:

Stream<X> stream = ...;
Stream<google::protobuf::Message> raw_stream = stream;

Again, I don't know how Stream<X> is implemented. All I know is that it contains a shared pointer to some implementation which generates messages.

like image 335
Zelta Avatar asked Oct 17 '22 19:10

Zelta


1 Answers

This:

template <typename T>
class Stream {
public:
  T* input();
};

is an object with one operation, which takes 0 arguments and returns a T*.

So is this:

std::function<T*()>

admittedly you invoke it like stream() instead of stream.input().

With this second solution, if U is a base of T, then you can convert the above to std::function<U*()>. Which solves your problem.

Personally I don't think typing .input between the name of your stream and () is worth a lot of work.

Type erasure that someone else has already done is best type erasure.

like image 188
Yakk - Adam Nevraumont Avatar answered Nov 01 '22 16:11

Yakk - Adam Nevraumont