如何使我自己的容器索引和可分配

时间:2016-10-05 14:10:43

标签: c++ c++11

我有一个像这样的容器类:

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},则上述行应该给我{ {1}}。因为a = {0,2,0,4}的{​​{1}}被a {0,2}编入{1,3}ac {0,0}设为$scope.allItems = $filter('filter')($scope.allItems , { property: !$scope.currentItem.property}); 。 怎么做?

2 个答案:

答案 0 :(得分:1)

数组视图是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>。另一个改进是hvae构造函数执行make_array_view所做的事情,也支持initializer_list,rvalues和原始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;
}

和一个组成两个排列。未检查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这样的库。

live example

您需要向begin添加迭代器和end / permuted_view。我将使迭代器存储指向视图和索引的指针,并在取消引用时使用operator[]上的view

如果使用array_view<T>迭代器的特化或子类将range_view<Iterator>重构为T*,则可以使用CRTP将range_view<Iterator>重构为range_helper<Iterator, Derived>。然后,对range_helperpermuted_view重用range_view,为range_view重用array_view。但这有点偏离预订。

各种库,包括Rangesv3和boost和C ++ 17 std和C ++ 20 std :: experimental等,都可以编写这些类型,或者更容易编写这些类型。

答案 1 :(得分:1)

您无法直接使用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构建新的A::Ref,因此您需要一个类似的构造函数:

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

无论如何,这是一个让你开始的简单概念,也是一个可以玩的东西。