Диапазон, основанный на неявно добавляет квалификатор `const`?

Посмотрим на следующий простой диапазон, основанный на цикле:

  int a = 5, b = 6;
  for (auto & i : {a, b})
  {
      std::cout << i << std::endl; // Works as expected.
      i = 3;                       // Error!
  }

gcc жалуется на assignment of read-only reference 'i', подразумевая, что диапазон, основанный на цикле, используемом с списком инициализаторов, неявно добавляет к ссылочной ссылке const квалификатор, полностью неспровоцированный.

  • Почему это происходит?
  • Есть ли работа, позволяющая изменять переменные в диапазоне, основанном на цикле?

Ответ 1

В

int a = 5, b = 6;
for (auto & i : {a, b})

У вас есть {a, b} - это std::initialiser_list двух элементов, a и b, в которых скопированы значения a и b. Теперь std::initializer_list предоставляет постоянные итераторы для своих элементов, потому что initializer_list неизменяемы, поэтому вы не можете привязать значение к ссылкам const lvalue.

Один вариант заключается в том, чтобы передать указатели вместо этого, что сделало бы указатели самими постоянными, но не значение, на которое они указывают:

for (auto& i : {&a, &b}) 
    *i = 0;

Live demo

Другой альтернативой могло бы быть использование std::reference_wrapper, но в этом случае потребовался бы вызов .get() или явный листинг static_cast<int&>:

for (auto& i : {std::ref(a), std::ref(b)}) 
    i.get() = 0;

Live demo

Учитывая, что std::reference_wrapper имеет оператор неявного преобразования в T&, я не удивлюсь, если в каком-то другом контексте вы сможете автоматически запускать неявное преобразование (в отличие от вызова .get()).


Также обратите внимание, что {a, b} не является диапазоном чисел от a до b, это действительно просто эти два числа. Таким образом, с int a = 0, b = 10 у вас не было бы [0, 10], а в списке 0, а затем 10.

Если вы хотите иметь "правильные" диапазоны, я рекомендую вам взглянуть на Boost.Range.

Ответ 2

Это не имеет ничего общего с циклами на основе диапазона. Проблема в том, что std::initializer_list<int>::iterator - const int*. Вы не можете изменить содержимое initializer_list. Если бы вы использовали тип типа std::vector<int>, это будет работать нормально.