Может ли С++ 11 на основе диапазона выполнять/проверять дополнительные операции/условия?
Я открываю цикл на основе С++ 11 и уже люблю его. Это заставляет вас экономить много времени при кодировании.
Тем не менее, я привык писать несколько циклов с дополнительными инструкциями/условиями, и мне интересно, что это может быть достигнуто при использовании цикла на основе С++ 11:
1. Дополнительная инкрементация
std::vector<int> v = { 1, 2, 3, 4, 5 };
size_t index = 0;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter, ++index )
{
std::cout << "v at index " << index << " is " << *iter;
}
Может стать:
size_t index = 0;
for ( int val : v )
{
std::cout << "v at index " << index << " is " << *iter;
++index;
}
Однако приращение index
в цикле for
лучше, потому что гарантировано (увеличивается, даже если цикл for
имеет continue
), например)
Есть ли способ переместить ++index
внутри оператора for
?
2. Динамический индекс динамической конверсии
std::vector<int> v = { 1, 2, 3, 4, 5 };
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter )
{
std::cout << "v at index " << ( iter - v.begin() ) << " is " << *iter;
}
Можно ли добиться чего-то подобного с помощью цикла С++ 11 с диапазоном? Есть ли способ узнать, сколько итераций было сделано до сих пор?
3. Дополнительное условие выхода
Я часто использую это в коде, где запрет запрещен как направляющая для кодирования:
std::vector<int> v = { 1, 2, 3, 4, 5 };
bool continueLoop = true;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end() && continueLoop; ++iter )
{
std::cout << "v value is " << *iter;
if ( *iter == 4 )
continueLoop = false;
}
Может ли быть что-то подобное достигнуто с помощью цикла на основе С++ 11 (выход из строя без использования перерыва)?
Ответы
Ответ 1
К сожалению, вы не можете поместить инкремент в диапазон, основанный на цикле. Однако в вашем конкретном случае - как std::vector
хранится его элементы в памяти - вы можете имитировать вариант 2, возвращаясь к указателям (спасибо @M.M и @Jarod42 за исправления и улучшения):
for ( const int& val : v ) {
std::cout << "v at index " << &val-v.data() << " is " << val;
}
более общий:
for ( const auto& val : v ) {
std::cout << "v at index " << std::addressof(val)-v.data() << " is " << val;
}
Другое, что вы можете сделать, это написать класс index_range
, представляющий коллекции индексов, по которым вы можете выполнять итерацию в своем диапазоне, основанном на цикле:
struct index_range_it {
size_t idx;
size_t operator*(){
return idx;
}
index_range_it& operator++() {
idx++;
return (*this);
}
};
bool operator!=(index_range_it l,index_range_it r) {
return l.idx != r.idx;
}
struct index_range {
size_t size;
index_range_it end(){return index_range_it{size};}
index_range_it begin(){return index_range_it{0};}
};
int main()
{
for (auto i: index_range{v.size()}){
std::cout << "v at index " << i << " is " << v[i];
}
}
Полноценная реализация этой идеи может быть найдена, например. здесь
Такой диапазон также может быть скомпонован в нечто, где итератор возвращает прокси-объект, содержащий индекс, а также ссылку на текущий объект и структурированное связывание С++ 17, которое было бы еще удобнее в использовании.
Ответ 2
Взгляните на range-v3 и cppitertools.
cppitertools обеспечивает очень удобное enumerate
:
std::vector<int> v = { 1, 2, 3, 4, 5 };
for (auto&& e : enumerate(v))
{
std::cout << "v at index " << e.index << " is " << e.element;
}
У Range-v3, к сожалению, нет перечисления, что меня очень огорчает, но вы можете составить свое собственное использование view::ints
и view::zip
*. Range-v3 имеет большое преимущество в том, что он является основой для предлагаемых диапазонов для стандартной библиотеки. Состав диапазона позволяет создавать чистые абстракции.
Что касается вашего последнего примера, я бы сказал, что вам следует избегать цикла вообще, если вам нужно уменьшить сложность. Вместо этого используйте соответствующий алгоритм, такой как std::find_if
, std::any_of
, который соответствует вашей задаче, не имея необходимости выражать поток управления.
Ответ 3
Для общего контейнера вы не можете получить индекс или итератор из цикла на основе диапазона. Вместо этого вам нужно либо сохранить отдельную переменную, либо вернуться в цикл итератора.
Итераторский взгляд можно записать немного проще, так как С++ 11:
for( auto iter = begin(v); iter != end(v); ++iter )
Для конкретного случая вектора вы можете сделать:
for ( auto& val : v )
{
cout << "Index is " << (&val - &v[0]) << '\n';
}
который работает, потому что векторы используют непрерывное хранилище.
Ответ 4
Вот что-то, что можно сделать # 2
#include <iterator>
#include <utility>
#include <type_traits>
#include <cstddef>
template<typename Range>
class RangeBasedAdaptor
{
Range& range;
public:
RangeBasedAdaptor(Range& r) : range(r) {}
struct iterator;
typedef typename std::remove_reference<decltype(*std::begin(range))>::type mapped_type;
typedef decltype(std::begin(range)) underlying_iterator;
struct value_type
{
std::size_t index() const { return idx; }
mapped_type& value() { return *ui; }
const mapped_type& value() const { return *ui; }
private:
std::size_t idx;
underlying_iterator ui;
friend
struct iterator;
};
struct iterator
{
iterator();
iterator& operator++() { ++val.ui; ++val.idx; return *this; }
value_type& operator*() { return val; }
bool operator!=(iterator other) { return val.ui != other.val.ui; }
private:
iterator( underlying_iterator ui, std::size_t idx ) { val.idx=idx; val.ui=ui; }
value_type val;
friend
class RangeBasedAdaptor;
};
iterator begin() { return iterator{ std::begin(range), 0 }; }
iterator end() { return iterator{ std::end(range), (std::size_t)-1 }; }
};
template<typename Range>
auto indexed(Range& r) -> RangeBasedAdaptor<Range>
{
return {r};
}
// -------------------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <list>
int main()
{
std::vector<int> foo = { 1,2,3,4,5,6 };
for( auto& val : indexed(foo) )
{
val.value() += 3;
std::cout << val.index() << " : " << val.value() << std::endl;
}
const std::list<float> foo2 = { 1.1f, 2.2f, 3.3f };
for( auto& val : indexed(foo2) )
{
std::cout << val.index() << " : " << val.value() << std::endl;
}
}
Он предназначен только для диапазонов, основанных на циклах, следовательно, минимального итератора.
Ответ 5
В компьютерных языках традиционно цикл "for" представляет собой цикл с указанными условиями цикла. Если программист хочет указать свои собственные условия цикла, они используют цикл while. С этой точки зрения, циклы на основе С++ для циклов - это первый раз, когда на самом деле у языка действительно была настоящая конструкция цикла for. Таким образом, может потребоваться программист на С++, чтобы обдумать тот факт, что если они не могут справиться с условиями цикла, генерируемыми компилятором, они должны использовать другую конструкцию.
С учетом того, что итераторы могут быть настраиваемыми объектами, вы можете делать все, что хотите, с циклом, основанным на диапазоне, создавая собственный итератор. Раньше я обычно обнаружил, что это усилие не стоит дополнительного кода, если вы не собираетесь повторно использовать этот итератор несколько раз.
1. Дополнительная инкрементация
Однако увеличение индекса в цикле for лучше, потому что гарантируется (увеличивается, даже если for loop имеет продолжение для пример)
Есть ли способ переместить индекc++ внутри оператора for?
Да, с пользовательским итератором. Однако, это много работы. Это легко:
for (auto element : container) {
++index;
}
Здесь мы также знаем, что его гарантированно получить приращение, потому что оно помещено наверху перед любыми возможными операциями break или continue.
- Получить динамический индекс динамически
Можно ли добиться чего-то подобного с помощью цикла С++ 11 с диапазоном? Является есть способ узнать, сколько итераций было сделано до сих пор?
Опять же, это можно сделать с помощью пользовательского итератора, но почти наверняка этого не стоит. Я должен был сделать это сам на прошлой неделе, и решение выглядело очень похоже на код в # 1 выше.
- Дополнительное условие выхода
Я часто использую это в коде, где break запрещен как кодирование СНИМИТЕ:
Это никогда не должно быть в руководстве по кодированию. Его плоская ошибка. Я не утверждаю, что вы нарушаете свои рекомендации. Но я утверждаю, что вы (и кто-либо еще читающий это) никогда больше не ставили никаких таких вещей в документ с рекомендациями по кодированию.
Для хорошего структурированного кодирования существует общее правило: у любого блока кода должна быть только одна точка выхода (aka: goto считается вредной), Однако цикл с двумя операторами вывода все еще имеет только одну точку выхода. Оба выхода возвращают управление в ту же точку вне цикла.
Более практично, существует много типов циклов, которые должны быть более сложными (например: сложнее понять и продолжать работать должным образом), если вы не можете поставить свой тестовый выход в середине. Если руководство обычно заставляет вас писать более тупой код, его плохой ориентир.
Опять же, вы можете обойти это с помощью пользовательского итератора. В этом случае я бы сказал, что это может быть способ пойти. Конечно, его тонны больше кода, чем его ценность, просто для того, чтобы обойти ваши глупые правила кодирования. Но это ошибка руководства, а не ваша.
Ответ 6
Я не буду писать код, который заменяет совершенно хороший оператор break
.
Получение индекса для вектора (то есть когда оно полезно) легко: итератор над auto& x:v
, а затем вычесть std::addressof(x)-v.data()
.
Что оставляет # 1.
template<class It, class Operation>
struct iterator_with_extra_increment_t {
using self=iterator_with_extra_increment_t;
It it;
Operation& op;
void operator++(){ ++it; op(); }
auto operator*()->decltype(*std::declval<It&>()) { return *it; }
friend bool operator!=(self const& lhs, self const& rhs){
return lhs.it != rhs.it;
}
friend bool operator==(self const& lhs, self const& rhs){
return lhs.it == rhs.it;
}
};
template<class It, class Operation>
iterator_with_extra_increment_t<It, Operation>
iterator_with_extra_increment( It it, Operation& operation ) {
return {std::move(it), operation};
}
template<class Range, class Modify>
struct iterate_modified_t {
Range r;
Modify m;
auto begin() { using std::begin; return m(begin(r)); }
auto end() { using std::end; return m(end(r)); }
};
template<class Range, class Modify>
iterate_modified_t<Range, std::decay_t<Modify>>
iterate_modified( Range&& r, Modify&& m) {
return {std::forward<Range>(r), std::forward<Modify>(m)};
}
template<class Range, class Op>
auto also_on_inc( Range&& r, Op&& op ) {
auto modify = [op = std::forward<Op>(op)](auto&& it) {
return iterator_with_extra_increment(decltype(it)(it), op);
};
return iterate_modified( std::forward<Range>(r), std::move(modify) );
}
теперь мы имеем also_on_inc
:
std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : also_on_inc(a, [&]{++count;}) ) {
std::cout << count << "->" << x << '\n';
}
живой пример.
Некоторый из вышеуказанного кода - С++ 14, потому что я слишком ленив, чтобы выписать предложения ->decltype
.
Мы можем улучшить этот синтаксис со злоупотреблением оператора, например:
std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} ) {
std::cout << count << "->" << x << '\n';
}
если мы безумны, что позволяет нам делать
std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} *also_on_inc* [&]{std::cout << count << '\n';} ) {
std::cout << count << "->" << x << '\n';
}
упрощение цепочки таких предложений.