reinterpret_cast从非常量版本到常量版本的模板类



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

我希望有常量的正确性,所以我正在使用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);
}

它似乎有效,但我不确定最后一个片段的正确性。


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

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
};

是的,该reinterpret_cast无效,您不能只将一个对象强制转换为另一个不相关类型的对象。好吧,你可以,但请不要从船尾访问它。

您可以添加转换运算符,而不是禁用隐式构造函数,该构造函数不起作用,因为您在非重载解析上下文中使用 SFINAE(有一些解决方法,例如使条件依赖于可实现相同目标)。但是使用转换运算符更清洁 IMO:

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

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

gcc 在-O1及以上版本上生成相同的代码,用于将ImageView<const char>ImageView<char>传递给foo,以及-O2上方的 clang 。因此,如果您使用优化进行编译,则完全没有区别。

即使const char可以隐式转换为charImageView<char>ImageView<const char>是完全不相关的类型,我在这里什么也没学到。但这是一种耻辱,因为从某种意义上说,ImageView<const char>可以修改的ImageView<char>

幸运的是,我们有一个工具来调用编译器的东西是别的东西。这是继承的定义(根据利斯科夫规则)。就是这样。让ImageView<const char>继承ImageView<char>解决你的大部分问题,这是有道理的:

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);
}

演示

事实上,使用 @YCS 的想法可以实现所需的行为,但它需要更复杂的代码和const_cast才能与m_data一起使用。const_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;
}

最新更新