如何使我自己的容器索引和可分配
how to make my own container indexed and assignable
我有一个这样的容器类:
class A {
public:
// assuming all the other operators used below
// have been defined.
// ...
A operator()(const A& a) const {
A r(a.size());
for (int i = 0;i < a.size();++i) r[i] = data[a[i]];
return r;
}
private:
std::vector<int> data;
};
所以我可以这样做:
A a, b, c;
// ... initialize data here...
c = a(b); // I can index a by b
现在我想让索引容器a(b)
可赋值,例如
a(b) = c;
例如,如果a
是{1, 2, 3, 4}
, b
是{0,2}
, c
是{0,0}
,上面的行应该给我a = {0,2,0,4}
。因为{0,2}
索引的a
是a
中的{1,3}
,设置它们为c {0,0}
会得到这个。怎么做呢? 不能直接使用A
类型。您需要一个中间类型来映射或引用回对象。作为一个简单的非最优示例:
#include <functional>
#include <vector>
class A
{
public:
class Ref
{
private:
friend class A;
std::vector<std::reference_wrapper<int>> refs;
Ref( A & obj, A & idx )
{
refs.reserve( idx.data.size() );
auto val_it = obj.data.begin();
for( auto i : idx.data ) {
ref.emplace_back( std::ref( data[i] ) );
}
}
public:
Ref & operator=( const A & obj )
{
auto obj_it = obj.data.begin();
for( auto ref : refs ) {
ref.get() = *obj_it++;
}
return this;
}
};
// ...
Ref operator()( const A & idx )
{
return Ref( *this, idx );
}
private:
std::vector<int> data;
};
然后事情开始变得棘手,因为你会想要能够在这些基于引用的视图和原始类型之间转换
简单的方法是保持const操作符的实现(注意我的操作符是非const):即它仍然返回类型A
,这是有意义的。但是您可能希望能够从A::Ref
构建新的A
,因此您需要这样的构造函数:
A::A( const Ref & r )
{
data.reserve( r.refs.size() );
auto r_it = r.refs.begin();
for( auto ref : r.refs ) {
data.push_back( ref.get() );
}
}
无论如何,这是一个简单的概念,可以让您开始使用。
数组视图是T
连续缓冲区中的视图:
template<class T>
struct array_view {
T* b=0;
T* e=0;
T* begin() const { return b; }
T* end() const { return e; }
std::size_t size() const { return end()-begin(); }
bool empty() const { return end()==begin(); }
T& operator[](std::size_t i)const { return begin()[i]; }
array_view( array_view const& ) = default;
array_view& operator=( array_view const& ) = default;
array_view() = default;
array_view( T* s, T* f ):b(s),e(f) {}
array_view( T* s, std::size_t n ):array_view(s, s+n) {}
};
template<class Src>
array_view< std::remove_reference_t<
decltype(*(std::declval<Src&>().data()))
> >
make_array_view( Src& src ) {
return {src.data(), src.size()};
}
(为了使它更好,做一般的"范围"升级。在上面的例子中,const
指的是"改变被查看的范围",而不是内容——如果你想要const
的内容,创建一个array_view<const T>
。另一个改进是让vae构造函数做make_array_view
做的事情,也支持initializer_list
、右值和原始C数组)。
给出T
的数组视图的排列视图。
首先,置换是从size_t的有界集合到另一个size_t的有界集合的函数。
struct permutation {
std::function< std::size_t(std::size_t) > mapping;
std::size_t count = 0;
std::size_t size() const { return count; }
permutation( std::function< std::size_t(std::size_t) > m, std::size_t c):
mapping(std::move(m)),
count(c)
{}
std::size_t operator()( std::size_t i )const {
return mapping(i);
}
};
这不是最安全的,因为我们没有检查输出范围是否合理。
工厂函数:
template<class T>
permutation make_permutation_from( T src ) {
auto size = src.size();
return {
[src = std::move(src)]( std::size_t in ) {
return src[in];
},
size
};
}
// optimization
permutation make_permutation_from( permutation src ) {
return src;
}
和1组成两个排列。未检查size
字段的有效性。
// if they don't align, we are screwed, but...
permutation chain_permutation( permutation first, permutation second ) {
auto first_size = first.size();
return {
[first=std::move(first), second=std::move(second)](std::size_t i){
return second(first(i));
},
first_size
};
}
这将我们引向permuted view
,它是array_view
的一个视图,用于排列索引。
template<class T>
struct permuted_view {
array_view<T> source;
permutation permute;
std::size_t size() const { return permute.size(); }
T& operator[]( std::size_t i ) const {
return source[ permute(i) ];
}
template<class Src>
void assign_from( Src const& src ) {
if (src.size() != size()) exit(-1);
for (std::size_t i = 0; i < size(); ++i)
(*this)[i] = src[i];
}
void operator=( permuted_view const& src ) { assign_from(src); }
template<class U>
void operator=( U const& src ) { assign_from(src); }
template<class U,
std::enable_if_t< !std::is_integral<U>{}, int> =0
>
permuted_view<T> operator[]( U u )const {
return {
source,
chain_permutation( make_permutation_from(std::move(u)), permute )
};
}
};
现在permuted_view<int>
是int
数组上的一个排列。
注意permuted_view
没有拥有任何东西。它只是指别人的存储。所有权是你必须自己解决的问题。也许可以通过智能指针,或者其他方式。
用于此目的的高效库可能具有写时复制稀疏数组。要做到这一点,需要做很多工作,或者你应该找一个像Eigen这样的库。
生活例子。
你需要添加一个迭代器和begin
/end
到permuted_view
。我会让迭代器存储一个指向视图的指针和一个索引,并在解引用时对view
使用operator[]
。
如果你用T*
迭代器的专门化或子类将array_view<T>
重构为range_view<Iterator>
,那么你可以用CRTP将range_view<Iterator>
重构为range_helper<Iterator, Derived>
。然后为permuted_view
和range_view
重用range_helper
,为array_view
重用range_view
。但这有点太过分了。
各种库,包括Rangesv3和boost以及c++ 17 std和c++ 20 std::experimental等,要么已经编写了这些类型,要么使编写这些类型变得更容易。
- 我可以为unordered_map分配特定的模组值吗?
- 一般采用可索引/可调用的线性组合
- 使用 std::vector 在类中分配索引
- 通过在编译时折叠带有索引的指针来分配 C 数组
- 在xarray中通过索引赋值会分配整个数组
- 在可拆卸线程完成操作时取消分配内存
- 在 c++ 中将数组的值分配给另一个数组的索引
- 可视化C++将分配移动到未初始化的对象?
- 在线程中使用堆可分配 >100MB 的 RAM
- 哪种类型特征表明该类型是可分配的?(元组,对)
- C++概念相同且可分配
- 如何使具有包含唯一指针的成员变量的类可复制可分配
- GCC C++11 删除移动可分配类的副本分配会阻止 std::sort 编译
- 如何使具有常量属性的类可分配
- 创建可分配的最大字符数组
- 如何使我自己的容器索引和可分配
- 为Fortran可分配程序在C中分配内存
- 移动包含向量<unique_ptr的可分配类<T>>
- 移动clang和gcc中可分配的lambda
- 对左值和右值的可分配引用