Я пытаюсь реализовать общий поток ввода объектов. То есть интерфейс или облегченный прокси для реализации. Детали реализации неизвестны, то есть пользователь моей библиотеки может написать свой собственный поток, скажем, протобуф-сообщений, передать его в мою библиотеку и вернуть, скажем, поток строк или любой другой поток. Я хотел бы сохранить общий интерфейс потока, чтобы пользователи могли писать свои собственные преобразования и строить конвейеры преобразования.
Интерфейс потока должен выглядеть следующим образом:
template <typename T>
class Stream {
public:
T* input();
}
При каждом вызове input()
возвращает следующий объект в потоке или нулевой указатель, если поток пустой.
Проблема в том, что я хотел бы, чтобы Stream<T>
был конвертируемым в Stream<U>
, если T*
конвертируемым в U*
.
Моя неудачная попытка состояла в том, чтобы использовать указатель на реализацию следующим образом:
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()); }
}
Конструктор из StreamImpl<T>
гарантирует, что void*
, возвращенный из input_raw()
, был получен путем приведения T
к void*
, поэтому static_cast<T*>
безопасен.
Однако, если я выполню какое-либо преобразование, это утверждение не будет верным. То есть строительство Stream<T>
из StreamImpl<U>
небезопасно, даже если U*
конвертируется в T*
.
Итак, мой вопрос, как мне решить эту проблему?
Я вижу следующие возможности:
сохранить конвертер (например, std::function<T*(void*)>
) в потоке и обновлять его при каждом приведении. Это кажется излишне дорогим;
сохранить результат static_cast<U*>((T*)0)
и добавить этот результат к указателю, полученному из input_raw()
. Это кажется излишне опасным;
добавьте второй параметр шаблона OrigT
и сохраните StreamImpl<OrigT>*
вместо сохранения StreamImplBase*
. Это ограничит возможные применения класса, которых я бы хотел избежать;
с использованием dynamic_cast
не вариант, потому что нельзя dynamic_cast
с void*
.
Есть ли другие возможности? Как другие реализуют что-то подобное?
Вот пример использования. Предположим, у нас есть сообщение protobuf X
. Я бы хотел, чтобы это работало:
Stream<X> stream = ...;
Stream<google::protobuf::Message> raw_stream = stream;
Опять же, я не знаю, как реализовано Stream<X>
. Все, что я знаю, это то, что он содержит общий указатель на некоторую реализацию, которая генерирует сообщения.