遍历容器或范围-constness问题

8

我正在尝试编写一个模板函数,用于计算某个集合中所有元素的总和-该集合可以指定为普通的STL容器或ranges-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版本刚刚从预览版中发布,支持ranges的主分支。然而,以上错误仍然存在。
使用ranges对象作为const&是否是错误的?我知道这些对象很轻量级,容易复制,但使用const引用不应该有影响,对吧?另一方面,如果传递了具体的STL容器,我需要将其传递为const&
如果使用const&是不正确的,那么是否有一种简单的方法可以使函数能够处理容器和ranges,而不需要在调用点编写任何内容(例如调用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; }
  );
}

这给我带来了与上述相同的转换错误。

使用cbegin()而不是begin()有帮助吗? - perivesta
我也曾这样想,但很快我就学会了'cbegin': is not a member of 'ranges::v3::remove_if_view....... - CygnusX1
1
std::cbegin/ranges::cbegin 这个免费函数能用吗? - felix
1
你能否添加你包含的ranges-v3头文件、你创建的range以及如何使用它调用你的sum函数?我尝试了for (const auto& v : range) { sum += extract(v);,并且在我的自己的range库中使用const range可以工作,但是使用迭代器时却不行(无法解释为什么)。 - Ted Lyngmo
1
@TedLyngmo 我提供了一个完整的例子。 - CygnusX1
显示剩余4条评论
1个回答

5
并非所有的范围都是const可迭代的。也就是说,存在一些范围类型T,其中const T并不是一个范围。 filter是经典示例:它需要缓存从begin返回的迭代器的值,以便将来的调用为O(1)(请参见http:// eel.is/c++draft/range.filter.view#6 )。因此,如果beginconst成员函数,则会违反标准库策略,即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; }
  );
}

它将会正确编译并运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接