Подтвердить что ты не робот

Может ли С++ 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 (выход из строя без использования перерыва)?

4b9b3361

Ответ 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.

  1. Получить динамический индекс динамически

Можно ли добиться чего-то подобного с помощью цикла С++ 11 с диапазоном? Является есть способ узнать, сколько итераций было сделано до сих пор?

Опять же, это можно сделать с помощью пользовательского итератора, но почти наверняка этого не стоит. Я должен был сделать это сам на прошлой неделе, и решение выглядело очень похоже на код в # 1 выше.

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

упрощение цепочки таких предложений.