遍历容器或范围-常量性问题

时间:2018-11-30 12:22:58

标签: c++ visual-c++ range-v3

我正在尝试编写一个模板函数,该函数将汇总某些集合的所有元素-指定为纯stl容器或range-v3的范围。 (如下所示,实际功能更为通用)我认为这可以工作:

template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
  using It = decltype(range.begin());
  Ret sum = zero;
  int numElements = 0;
  for (It it = range.begin(); it != range.end(); ++it) {
    sum += extract(*it);
    ++numElements;
  }
  return { sum, numElements };
}

这确实适用于STL元素,但不适用于范围。这给了我一个很长的错误:

<this file, at line 'using It'> error C2662: 'ranges::v3::basic_iterator<ranges::v3::adaptor_cursor<ranges::v3::basic_iterator<ranges::v3::adaptor_cursor<std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<_Ty>>>,ranges::v3::iter_transform_view<Rng,ranges::v3::indirected<Fun>>::adaptor<false>>>,ranges::v3::remove_if_view<ranges::v3::transform_view<Rng,Fun>,ranges::v3::logical_negate_<EnemyGroup::stepUpdate::<lambda_c582fb1297dce111c4572cef649d86b9>>>::adaptor>> ranges::v3::view_facade<Derived,ranges::v3::finite>::begin<Derived,false,0x0>(void)': cannot convert 'this' pointer from 'const Range' to 'ranges::v3::view_facade<Derived,ranges::v3::finite> &'
note: Conversion loses qualifiers

最初,我认为这是Ranges-v3的vs2015分支的不足之处。没多想,我只是破解了一个快速解决方案:

template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
  using It = decltype(const_cast<Range*>(&range)->begin());
  Ret sum = zero;
  int numElements = 0;
  for (It it = const_cast<Range*>(&range)->begin(); it != const_cast<Range*>(&range)->end(); ++it) {
    //sum += extract(std::as_const(*it)); (does not work either, converts to void)
    sum += extract(*it);
    ++numElements;
  }
  return { sum, numElements };
}

但是,由于预览中刚刚发布了最新的MSVC版本,现在正式支持range的master分支。但是,以上错误仍然普遍。

  • 将范围对象用作const&是错误的做法吗?我知道这些对象是轻量级的,并且很容易复制,但是使用const引用应该不会受到伤害,或者?另一方面,如果传递了一个具体的STL容器,则需要以const&
  • 的形式传递
  • 如果使用const&不正确,是否有一些简单的方法可以使函数同时使用容器和范围,而无需在调用站点编写任何内容(例如,调用view::all

我正在使用Visual Studio Community 2017版本15.9.3。请注意,在15.9之前的版本中,不支持其主分支中的range-v3


由于您在问我怎么称呼它。我的实际代码很复杂,但是我将其简化为这个小示例:

#include <set>
#include <range/v3/view/filter.hpp>

template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(const Range& range, Ret zero, Func extract) {
  using It = decltype(range.begin());
  Ret sum = zero;
  int numElements = 0;
  for (It it = range.begin(); it != range.end(); ++it) {
    sum += extract(*it);
    ++numElements;
  }
  return { sum, numElements };
}

int main() {
  std::set<int*> units;
  auto [vsum, num] = sum(
    units | ranges::v3::view::filter([](const int* eu) { return *eu>0; }),
    0,
    [](const int* eu) { return *eu/2; }
  );
}

这给了我与上面相同的转换错误。

1 个答案:

答案 0 :(得分:5)

并非所有范围都是const可重复的。也就是说,存在T不是范围的范围类型const Tfilter是经典示例:它需要缓存从begin返回的迭代器的值,以便将来的调用为O(1)(请参见http://eel.is/c++draft/range.filter.view#6)。因此,begin不能是const的成员函数,而又不会违反标准库的政策,即const成员可以在不引入数据竞争的情况下从多个线程调用。

因此,const Range&并不是接受通用的Range参数,而是接受“我不打算修改的容器”。我们建议采用Range参数的函数通过转发引用来接受它们。如果您将程序更改为:

#include <set>
#include <range/v3/view/filter.hpp>

template <typename Range, typename Ret, typename Func>
std::pair<Ret, int> sum(Range&& range, Ret zero, Func extract) { // Note "Range&&"
  Ret sum = zero;
  int numElements = 0;
  for (auto&& e : range) {
    sum += extract(e);
    ++numElements;
  }
  return { sum, numElements };
}

int main() {
  std::set<int*> units;
  auto [vsum, num] = sum(
    units | ranges::v3::view::filter([](const int* eu) { return *eu>0; }),
    0,
    [](const int* eu) { return *eu/2; }
  );
}

它将编译并正确运行。