8

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.

CygnusX1
  • 20,968
  • 5
  • 65
  • 109
  • Does using `cbegin()` instead of `begin()` help? – perivesta Nov 30 '18 at 12:27
  • Thought that too, but I quickly learnd that `'cbegin': is not a member of 'ranges::v3::remove_if_view.......` – CygnusX1 Nov 30 '18 at 12:37
  • 1
    Does free function `std::cbegin/ranges::cbegin` work? – felix Nov 30 '18 at 12:40
  • Nope. `using It = decltype(ranges::v3::cbegin(range));` gives me `'operator __surrogate_func': no matching overloaded function found`, followed by `error C2893: Failed to specialize function template 'unknown-type ranges::v3::_cbegin_::fn::operator ()(const R &&) noexcept() const'` – CygnusX1 Nov 30 '18 at 12:49
  • 1
    Can you add which ranges-v3 header you include, the range you create and how you call your `sum` function with it? I tried `for (const auto& v : range) { sum += extract(v);` and that worked on my own range lib with a `const` range, but with the iterator, it didn't (can't explain why). – Ted Lyngmo Nov 30 '18 at 13:47
  • Can't tell you why this isn't working but another workaround could be to make the fucntion `template std::pair sum(Range& range, Ret zero, Func extract) { code here }` and `template std::pair sum(Range&& range, Ret zero, Func extract) { return sum(range, zerp, extract); }` – NathanOliver Nov 30 '18 at 13:50
  • Simplification idea, you can lose the `It` type alias and just do `for ( auto it = ... ` This may magically fix your const problem as well. – Ben Voigt Nov 30 '18 at 15:56
  • I'd say that the problem is in your functor. Which one are you using? Note that `begin` applied to a `const Range` returns a `const_iterator` and a lambda taking an `auto &` may be problematic. I have just tested `std::cout << sum(view::reverse(view::iota(0u,100u)),0u,[](auto i) { return 2.0*i; }).first;` and in works fine under VS2015 – metalfox Nov 30 '18 at 16:00
  • 1
    @TedLyngmo I provided a complete example – CygnusX1 Nov 30 '18 at 16:49

1 Answers1

5

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.

Casey
  • 41,449
  • 7
  • 95
  • 125