实现Stream <t>,可以转换为Stream <u>,其中U是T

时间:2018-04-27 23:16:05

标签: c++ pointers casting

我正在尝试实现对象的通用输入流。也就是说,实现的接口或轻量级代理。实现的细节是未知的,即我的库的用户可以编写自己的例如protobuf消息流,将其传递给我的库并返回(例如)字符串流或任何其他流。我想保持流通用的接口,以便用户可以编写自己的转换并构建转换管道。

流的接口应如下所示:

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>是如何实现的。我所知道的是它包含一个指向生成消息的实现的共享指针。

2 个答案:

答案 0 :(得分:2)

此:

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

是一个具有一个操作的对象,它接受0个参数并返回T*

这就是:

std::function<T*()>

不可否认,您调用stream()而不是stream.input()

使用第二个解决方案,如果UT的基础,那么您可以将上述内容转换为std::function<U*()>。这解决了你的问题。

就我个人而言,我不认为在您的信息流名称和.input之间输入()值得做很多工作。

其他人已经完成的类型擦除是最佳类型擦除。

答案 1 :(得分:1)

当在同一个函数中不知道两个类时,有一个C ++特性允许从派生类转换为基类:exception。当然,这是一种丑陋,丑陋的滥用,但它确实有效:

#include <type_traits>
#include <stdexcept>

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

template <typename T>
class StreamImpl : public StreamImplBase {
public:
    virtual T* input() = 0;
    void toss_input() override
    { throw input(); }
};

template <typename T>
class Stream {
    StreamImplBase* impl;
public:
    template <typename U,
        std::enable_if_t<std::is_convertible<U*, T*>::value>* = nullptr>
    explicit Stream(StreamImpl<U>* impl) : impl(impl) {}

    template <typename U,
        std::enable_if_t<std::is_convertible<U*, T*>::value>* = nullptr>
    Stream(const Stream<U>& str) : impl(str.impl) {}

    T* input() const
    {
        try {
            impl->toss_input();
        } catch (T* ptr) {
            return ptr;
        }
        throw std::logic_error("Stream logic is broken?");
    }
};

查看complete example use on coliru。使用std::shared_ptr<StreamImpl<U>>std::shared_ptr<StreamImplBase>可以改善这种情况。