Перебор контейнера или диапазона - проблема с константностью - PullRequest
0 голосов
/ 30 ноября 2018

Я пытаюсь написать шаблонную функцию, которая будет суммировать все элементы некоторой коллекции - заданной либо как простой контейнер 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

Первоначально я думал, что это был некоторый недостаток ветки vs2015 range-v3.Недолго думая, я просто взломал быстрый обход:

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, которая только что вышла из предварительного просмотра, теперь официально поддерживается основная ветвь диапазонов.Тем не менее, вышеупомянутая ошибка преобладает.

  • Является ли использование объектов диапазона как 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 голосов
/ 30 ноября 2018

Не все диапазоны const -регулируемы.То есть существуют типы диапазонов T, для которых const T не является диапазоном.filter является классическим примером: ему необходимо кэшировать значение итератора, возвращенного из 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; }
  );
}

Она скомпилируется ибеги правильно.

...