具有特殊情况实现的迭代器

时间:2017-10-19 13:40:42

标签: c++ c++11 boost iterator

有没有办法构建一个包含两个实现的迭代器类:一个包含任意数量元素的容器的一般实现和一个特殊情况(非常快)的实现,当容器包含一个不使用虚函数和动态的元素时多态?

目前,我有:

struct Container { 
    struct FastIterator;
    struct SlowIterator;
    void add(...) { ... }
    SlowIterator begin_slow() { ... }
    FastIterator begin_fast() { ... }
};
相反,我希望:

struct Container { 
    struct Iterator;
    void add(...) { ... }
    Iterator begin() { // select between fast and slow based on the contents of the container }
};

这样:

void f() {
    Container c;
    c.add(...);
    Container::Iterator it = c.begin(); // uses FastIterator hidden by the Iterator type
}

void f2() {
    Container c;
    c.add(...);
    c.add(...);
    Container::Iterator it = c.begin(); // use SlowIterator hidden by the iterator type
}

当然,显而易见的方法是在Iterator实现中使用虚函数或委托来从一个案例切换到另一个案例,但是我测试了与直接使用Slow / Fast相比,这减缓了很多次迭代迭代器。

由于在调用begin()期间决定使用哪个实现的所有信息都可用,我认为有一种方法可以使用某种编译时多态/技巧来避免任何类型的间接。

另外,我真的不希望用户必须决定是否应该调用begin_fast()或begin_slow(),这应该由Iterator类自动处理和隐藏。

有办法吗?

由于

1 个答案:

答案 0 :(得分:2)

不确定

您的容器变为两个不同状态的std::variant,即“单个元素”状态和“多个元素”状态(可能是“零元素”状态)。

成员函数add可以将零元素或单元素容器转换为单元素或多元素函数。同样,在某些情况下,remove可能会相反。

变体本身没有beginend。相反,用户必须std::visit使用可以接受的函数对象。

template<class T>
struct Container:
  std::variant<std::array<T,0>, std::array<T,1>, std::vector<T>>
{
  void add(T t) {
    std::visit(
      overload(
        [&](std::array<T,0>& self) {
          *this = std::array<T,1>{{std::move(t)}};
        },
        [&](std::array<T,1>& self) {
          std::array<T,1> tmp = std::move(self);
          *this = std::vector<T>{tmp[0], std::move(t)};
        },
        [&](std::vector<T>& self) {
          self.push_back( std::move(t) );
        }
      ),
      *this
    );
  }
};

boostvariant,效果相似。 overload仅仅是

struct tag {};
template<class...Fs>
struct overload_t {overload_t(tag){}};
template<class F0, class F1, class...Fs>
struct overload_t: overload_t<F0>, overload_t<F1, Fs...> {
  using overload_t<F0>::operator();
  using overload_t<F1, Fs...>::operator();

  template<class A0, class A1, class...Args>
  overload_t( tag, A0&&a0, A1&&a1, Args&&...args ):
    overload_t<F0>( tag{}, std::forward<A0>(a0)),
    overload_t<F1, Fs...>(tag{}, std::forward<A1>(a1), std::forward<Args>(args)...)
  {}
};

template<class F>
struct overload_t:F {
  using F::operator();
  template<class A>
  overload_t( tag, A&& a ):F(std::forward<A>(a)){}
};

template<class...Fs>
overload_t<std::decay_t<Fs>...> overload(Fs&&...fs) {
  return {tag{}, std::forward<Fs>(fs)...};
}

overload非常容易:

template<class...Fs>
struct overload:Fs{
  using Fs::operator();
};
template<class...Fs>
overload->overload<Fs...>;

并使用{}代替()

中使用此内容如下:

Container<int> bob = get_container();

std::visit( [](auto&& bob){
  for (int x:bob) {
    std::cout << x << "\n";
  }
}, bob );

对于0和1的情况,循环的大小将完全为编译器所知。

中,您必须编写外部模板函数对象而不是内联lambda。

您可以将variant部分移出Container并转移到begin返回(迭代器内部),但这需要复杂的分支迭代器实现或访问者来访问在迭代器上。并且由于开始/结束迭代器类型可能是并列的,所以无论如何都要返回一个范围,这样访问才有意义。无论如何,这会让你回到Container解决方案的一半。

您也可以在variant之外实现此功能,但作为一般规则,对变量的早期操作不能更改同一代码范围中的更新类型。 可以用于在“继续传递样式”中传递的可调用对象上进行调度,其中两个实现都将被编译,但在运行时选择一个(通过分支)。编译器有可能实现访问将关闭哪个分支而死代码消除另一个分支,但另一个分支仍然需要是有效代码。

如果你想要完全动态类型化的对象,你至少会失去2到10倍的速度(这是支持它的语言),这很难通过一个元素循环的迭代效率来恢复。这与在返回的迭代器中存储变量等效(可能是虚拟接口或其他)并使其在运行时复杂地处理分支有关。由于你的目标是表现,这是不切实际的。

理论上,C ++可以根据对它们的操作来改变变量的类型。即,理论语言

Container c;

是“空容器”类型,然后是:

c.add(foo);

现在c将静态类型更改为“单个元素容器”,然后

c.add(foo);

c将静态类型更改为“多元素容器”。

但那不是C ++类型的模型。您可以像上面一样(在运行时)模拟它,但它不一样。