我正在接收低级
{ void * data; uint stride, count; }
格式的托管数据。我可以读取、写入和交换数据项,但不能添加或删除/调整大小/重新分配。有足够的信息用于迭代和随机访问,但没有用于取消引用的类型信息。所有不透明的项目都可以轻松复制和移动。
在现代 C++ 中是否有可能促进这种数据格式的 std 兼容性,省略解引用接口,并将评估委托给外部上下文?比如:
std::sort(OpaqueFirst, OpaqueLast, [](Opaque a, Opaque b){ return handle(a, b); });
基本思想是创建一个类型,引用到你的一个子数组,并定义
swap
来实际交换它们的内容:
struct buffer {std::span<std::byte> s;}; // but see below
void swap(const buffer &a,const buffer &b) {
assert(a.s.size()==b.s.size());
auto i=b.s.begin();
for(auto &x : a.s) std::swap(x,*i++);
}
然后我们只需要创建此类对象的惰性范围,这很容易将
std::views::transform
包裹在 std::views::iota
周围,并根据问题编写简单的比较器:
struct array { void * data; uint stride, count; };
void sort(const array &a,bool lt(const void*,const void*)) {
std::ranges::sort
(std::views::transform
(std::views::iota(uint(),a.count),
[&](uint i)
{return buffer{{static_cast<std::byte*>(a.data)+a.stride*i,a.stride}};}),
[&](const buffer &x,const buffer &y) {return lt(x.s.data(),y.s.data());});
}
不幸的是,
sort
要求能够将范围元素移动到临时变量中,而不仅仅是交换它们。这需要一个相当复杂的包装类型,它实际上可以暂时拥有数据,这反过来又需要动态分配,因为array::stride
不是常量:
struct buffer {
std::unique_ptr<std::byte[]> u;
std::span<std::byte> s;
buffer(std::span<std::byte> s) : s(s) {}
buffer(buffer &&b) noexcept {
if(b.u) u=std::move(b.u);
else {
u=std::make_unique<std::byte[]>(b.s.size());
std::ranges::copy(b.s,u.get());
s={u.get(),b.s.size()};
}
}
void operator=(const buffer &b) const noexcept;
buffer& operator=(const buffer &b) noexcept {
assert(s.size()==b.s.size());
std::ranges::copy(b.s,s.begin());
return *this;
}
};
保留我们的自定义
swap
可以避免对每个元素交换使用此类分配,因此性能并不糟糕。未实现的 operator=(…) const
是让库相信 buffer
是一个 proxy 类型,应该支持通过纯右值排序的必要条件。
编译器资源管理器上存在具有完整buffer
类型的
完整示例。