【问题标题】:reinterpret_cast template class from non-const to const versionreinterpret_cast 模板类从非常量版本到常量版本
【发布时间】:2018-03-26 11:57:23
【问题描述】:

我有模板类ImageView<Pixel>,它存储了一个指向数据和图像大小的非拥有指针。

我想要 const 正确性,所以我同时使用 Pixelconst Pixel

std::byte * data;
ImageView<char> img(data, width, height);
std::byte const* cdata;
ImageView<char> img(cdata, width, height); // compile error
ImageView<const char> cimg(cdata, width, height);

但是这样的代码当然会导致问题:

void foo(ImageView<const char> const&);

ImageView<char> view;
foo(view); // conversion from ImageView<char> to ImageView<const char> const& required

明显的解决方案是使用构造函数添加隐式转换:

template <class = std::enable_if_t<std::is_const_v<Pixel>>>
constexpr ImageView(ImageView<std::remove_const_t<Pixel>> const& other) noexcept
    : m_data(other.GetData())
    , m_stride(other.GetStride())
    , m_width(other.GetWidth())
    , m_height(other.GetHeight())
{}

但它的缺点是在每次转换时都会创建临时文件,ImageView 在大多数 64 位平台上是 24 字节。这个临时文件与原始文件的不同之处仅在于类型——它们的布局完全相同。于是我开始考虑使用reinterpret_cast和const引用转换运算符:

template <class = std::enable_if_t<!std::is_const_v<Pixel>>>
constexpr operator ImageView<std::add_const_t<Pixel>> const&() const noexcept
{
    using ConstImageView = ImageView<std::add_const_t<Pixel>>;
    return *reinterpret_cast<ConstImageView const*>(this);
}

似乎可行,但我不确定最后一个 sn-p 的正确性。


整个类的简化版(仅省略了一些额外的非虚函数):

template <class Pixel>
class ImageView
{
    template <class T, class U>
    using copy_const_qualifier =
        std::conditional_t<
            std::is_const_v<T>,
            std::add_const_t<U>,
            std::remove_const_t<U>>;

    using Byte = copy_const_qualifier<Pixel, std::byte>;

public:

    constexpr ImageView(Byte * data, unsigned w, unsigned h, std::size_t s) noexcept
        : m_data(data)
        , m_stride(s)
        , m_width(w)
        , m_height(h)
    {}

    constexpr Byte * GetData() const noexcept { return m_data; }
    constexpr std::size_t GetStride() const noexcept { return m_stride; }
    constexpr unsigned GetWidth() const noexcept { return m_width; }
    constexpr unsigned GetHeight() const noexcept { return m_height; }

protected:
    Byte * m_data;
    std::size_t m_stride; // in bytes
    unsigned m_width; // in pixels
    unsigned m_height; // in pixels
};

【问题讨论】:

  • 为什么要担心 24byte。过早的优化是根本...

标签: c++ templates type-conversion constants


【解决方案1】:

是的,reinterpret_cast 是无效的,你不能将一个对象转换为另一个不相关类型的对象。好吧,你可以,但请不要再访问它。

您可以添加一个转换运算符,而不是禁用隐式构造函数,因为您在非重载解析上下文中使用 SFINAE(有一些变通方法,例如使条件相关,这将实现相同的效果)目标)。但是使用转换运算符更干净IMO:

operator ImageView<const Pixel>() { return {m_data, m_width, m_height, m_stride}; }

您不必担心复制,编译器很聪明! :) 而且 24 字节真的没什么好担心的。

您自己查看程序集here。 gcc 在-O1 及以上生成相同的代码,用于将ImageView&lt;const char&gt;ImageView&lt;char&gt; 传递给foo,并用于-O2 以上的clang。因此,如果您使用优化进行编译,则完全没有区别。

【讨论】:

    【解决方案2】:

    尽管 const char 可以隐式转换为 charImageView&lt;char&gt;ImageView&lt;const char&gt; 是完全不相关的类型,但我在这里什么也没学到。但这是一种耻辱,因为从某种意义上说,ImageView&lt;const char&gt; ImageView&lt;char&gt;,可以对其进行修改。

    幸运的是,我们有一个工具可以告诉编译器一些东西是一个别的东西。这是继承的定义(根据 Liskov 规则)。就是这样。让ImageView&lt;const char&gt; inheritImageView&lt;char&gt; 解决您的大部分问题,这是有道理的:

    template<class T>
    struct ImageView {};
    
    template<class T>
    struct ImageView<const T> : ImageView<T>
    {};
    
    void f(ImageView<char>&) {}
    void f_const(ImageView<const char>&) {}
    
    int main()
    {
        ImageView<char> d1;
        ImageView<const char> d2;
    
        f(d1);
        f(d2);
        //f_const(d1); // error: invalid initialization of reference of type 'ImageView<const char>&' from expression of type 'ImageView<char>'
        f_const(d2);
    }
    

    DEMO

    【讨论】:

    • 不要认为这是 OP 想要的。他们希望仍然拥有const。如果传入的类型是constGetData() 返回指向const 的指针,这不是你在做的。
    • 这真是一个有趣的解决方案,但它只解决了一半的问题。使用您的代码,我的第一个 sn-p 将无法编译,因为内部 m_data 将是非常量的。但也许我能够弄清楚如何让所有事情都符合这个想法。
    • @Rakete1111 是的,它解决了几乎所有问题。关于ByteGetData 的定义,需要一个解决方法。我认为 OP 可以做到这一点。
    • 例如:coliru.stacked-crooked.com/a/e24d863a4ad36b0e(只是对可能发生的事情的想法)(我什至不确定这些极端是否真的必要)
    • ImageView&lt;const char&gt; 不是ImageView&lt;char&gt; 的一种,因此在这个方向上的继承毫无意义。它可以为您编译,因为您已经方便地从这两种类型中删除了所有有意义的部分。
    【解决方案3】:

    确实可以使用@YCS's idea 实现所需的行为,但它需要更复杂的代码和const_casts 才能使用m_dataconst_cast 这里是安全的,因为非常量 ImageView 的构造函数只接受指向非常量数据的指针。

    所以现在,我将保留带有转换构造函数或运算符的版本。如果我注意到临时变量对性能的重大影响,我将返回此代码:

    template <class Pixel>
    struct ImageView : public ImageView<const Pixel>
    {
        constexpr ImageView(Pixel * data) noexcept
            : ImageView(data)
        {}
    
        constexpr Pixel * GetData() const noexcept
        {
            const Pixel * data = ImageView<const Pixel>::GetData();
            return const_cast<Pixel*>(data);
        }
    };
    
    template <class Pixel>
    struct ImageView<const Pixel>
    {
        constexpr ImageView(const Pixel * data) noexcept
            : m_data(data)
        {}
    
        constexpr const Pixel * GetData() const noexcept
        {
            return m_data;
        }
    
    private:
        const Pixel * m_data;
    };
    
    int main()
    {
        int * data = nullptr;
        const int * cdata = nullptr;
    
        ImageView<int> img(data);
        //ImageView<int> img1(cdata); // compile error
        ImageView<const int> cimg(data);
        ImageView<const int> cimg1(cdata);
    
        auto img2 = img;
        auto cimg2 = cimg;
    
        ImageView<const int> cimg3(img);
        ImageView<const int> cimg4 = static_cast<ImageView<const int>>(img);
        ImageView<const int> cimg5 = img;
    
        img.GetData();
        cimg.GetData();
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2013-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多