是否有用于正向和反向迭代器的迭代器包装器?

时间:2019-03-29 17:18:47

标签: c++ iterator type-erasure

在一段代码中,我有四个std::vector<T>::iterator:两个普通的迭代器和两个reverse_iterator-我们将其称为forward1forward2reverse1reverse2

我需要根据它们所指向的对象的属性来精确地增加这些迭代器之一。具体来说,我需要增加其相应对象具有最大x值(其中x是某个字段)的迭代器。理想情况下,我会做类似的事情

Iter iters[] = {forward1, forward2, reverse1, reverse2}

// increment the appropriate iterator:
Iter &it = *std::max_element(iters.begin(), iters.end(), [](Iter &lhs, Iter &rhs){ return lhs->x < rhs->x; })
++it;

但是我找不到合适的基本类型或包装器(在上面的代码段中用Iter表示)。

那么,有什么方法可以做到而不必做下面的“手动”和丑陋的事情吗?也许可以扩展到任意数量的迭代器?

if      (forward1->x < forward2->x and [...] and forward1->x < reverse2->x)
    ++forward1;
else if (forward2->x < forward1->x and [...] and forward2->x < reverse2->x)
    ++forward2;
// etc...

此外,由于需要保留增量的方向,因此我无法使用reverse_iterator::base():也就是说,如果原始迭代器是反向的,则需要将其递增为反向,反之亦然。

如果我要使用boost::any_iterator,性能成本会很高吗?我正在循环执行此操作。

4 个答案:

答案 0 :(得分:2)

  

性能成本会很高吗?

也许。这取决于。您必须采取措施才能找出答案。

您可以直接使用boost::iterator_facade模板-该模板用于实现any_iterator

std::variant<Iter, std::reverse_iterator<Iter>>可能是此用例的轻量替代方案。

答案 1 :(得分:1)

template<class Scalar>
struct pseudo_it_ref_vtable {
  void(*inc)(void*) = 0;
  Scalar&(*get)(void*) = 0;

  template<class It>
  static pseudo_it_ref_vtable create() {
    return {
      [](void* pvoid){ ++*static_cast<It*>(pvoid); },
      [](void* pvoid)->Scalar&{ return **static_cast<It*>(pvoid); }
    };
  }

  template<class It>
  static pseudo_it_ref_vtable const* get_ptr() {
    static const auto vtable = create<It>();
    return &vtable;
  }
};

template<class Scalar>
struct pseudo_it_ref {
  using vtable_t = pseudo_it_ref_vtable<Scalar>;
  vtable_t const* vtable = nullptr;
  void* state = nullptr;
  pseudo_it_ref( pseudo_it_ref const& ) = default;
  pseudo_it_ref() = delete;
  template<class It,
    std::enable_if_t<!std::is_same<std::decay_t<It>, pseudo_it_ref>{}, bool> = true
  >
  pseudo_it_ref( It& it ):
    vtable(vtable_t::template get_ptr<It>()),
    state( std::addressof(it) )
  {}
  void operator++() { vtable->inc(state); }
  Scalar& operator*() { return vtable->get(state); }
  Scalar* operator->() { return std::addressof(**this); }
};

这是一种简单的类型擦除类型,可以存储对Scalar&上任何迭代器的引用。

pseudo_it_ref<int> iters[] = {forward1, forward2, reverse1, reverse2};

// increment the appropriate iterator:
auto &it = *std::max_element(iters.begin(), iters.end(), [](Iter &lhs, Iter &rhs){ return lhs->x < rhs->x; })
++it;

可能有更简单的方法。

Live example

答案 2 :(得分:1)

我认为stl中没有什么可以为您完成工作。

听起来您需要将包含所有迭代器的简单帮助程序类放在一起,这些类可以硬连接以获取所需类型的四个迭代器,也可以进行模板化。这里“最聪明的”将是使用可以接受任意数量的迭代器的var-arg模板和一个值比较器。当每个迭代器到达end()时,代码也需要应付。实际上,它需要知道它们是否等于它们的end(),因此构造函数必须是迭代器范围对的列表。

但是,除了增加最低的迭代器之外,还有一个问题,关于迭代器,或者它代表的值,您还需要访问什么?即您是否需要读取当前的最小值,还是需要访问迭代器才能访问整个对象,例如一对在std :: map中?或删除它?

问的明显原因是因为该类将只能返回一种类型的对象来表示“当前”迭代值。为此,您需要确保所有迭代器都包含相同的value对象,或者它们都从通用的可返回迭代器对象派生。

答案 3 :(得分:1)

以下函数incrementMax是C ++ 14及更高版本的通用解决方案。 此函数将存储最大值的...args的第一个递增。

此函数首先生成values s的值数组f(*iterator),然后找到与最大值对应的位置idx。 函数increment递增其指定的迭代器。 此函数使用void函数arr的数组increment_impl并调用其中的第index

template<int N, class Tuple>
void increment_impl(Tuple& t)
{
    ++std::get<N>(t);
}

template<class Tuple, std::size_t... Is>
void increment(Tuple& t, std::size_t index, std::index_sequence<Is...>)
{
    using void_f = void(*)(Tuple&);
    static constexpr void_f arr[] = { &increment_impl<Is, Tuple>... };

    arr[index](t);
}

template<class F, class ...Args>
void incrementMax(F f, Args& ...args)
{
    const auto values = { f(*args)... };
    const auto it = std::max_element(std::begin(values), std::end(values));
    const auto idx = std::distance(std::begin(values), it);

    auto t = std::forward_as_tuple(args...);
    increment(t, idx, std::make_index_sequence<sizeof...(Args)>{});
}

这是一个用法示例:

DEMO

DEMO (6 elements case)

struct S
{
    int x;
};

int main()
{
    std::vector<S> v = {{1}, {2}, {3}, {4}, {5}};
    auto forward1 = v.begin();    // 1
    auto forward2 = forward1+1;   // 2
    auto reverse1 = v.rbegin()+1; // 4, maximum
    auto reverse2 = v.rbegin()+2; // 3

    // 1243
    std::cout << forward1->x << forward2->x << reverse1->x << reverse2->x << std::endl;    

    // do ++reverse1;
    incrementMax([](const S& s){ return s.x; }, forward1, forward2, reverse1, reverse2);

    // 1233
    std::cout << forward1->x << forward2->x << reverse1->x << reverse2->x << std::endl;    

    return 0;
}