Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating over container or range - problem with constness

I am trying to write a template function that will sum up all elements of some collection - specified either as a plain stl container, or as a ranges-v3's range. (The actual function, as shown below is a bit more generic) I thought this would work:

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

This indeed works for STL elements, but not for ranges. This gives me a very long error:

<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

originally, I thought it was some deficiency of the vs2015 branch of ranges-v3. Without thinking much, I just hacked a quick walkaround:

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

but with the newest MSVC version that just came out from preview, the master branch of ranges is now officially supported. Yet, the above error prevails.

  • Is using range's objects as const& a wrong thing to do? I know these objects are lightweight and are easy to copy around, but using a const reference shouldn't hurt, or? On the other hand, if a concrete STL container is passed, I need it to be passed as const&
  • If using const& is incorrect, is there some easy way to have a function work with both containers and ranges, without writing anything at the call site (e.g. invoking view::all)

I am using Visual Studio Community 2017, Version 15.9.3. Note, that before 15.9, range-v3 in its master branch was not supported.


Since you are asking how exactly I call it. My actual code is complicated, but I reduced it down to this small example:

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

This gives me the same conversion errors as above.

like image 408
CygnusX1 Avatar asked Nov 30 '18 12:11

CygnusX1


1 Answers

Not all ranges are const-iterable. That is, there are range types T for which const T is not a range. filter is the classic example: it needs to cache the value of the iterator returned from begin so that future calls are O(1) (See http://eel.is/c++draft/range.filter.view#6). Consequently, begin cannot be a const member function without violating the Standard Library policy that const members are callable from multiple threads without introducing data races.

As a consequence, const Range& isn't idiomatic for accepting general Range arguments as it was for accepting "container that I don't intend to modify." We recommend that functions that take Range arguments accept them by forwarding reference. If you alter your program to:

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

It will compile and run correctly.

like image 114
Casey Avatar answered Oct 29 '22 20:10

Casey