Итерировать вектор с «сгруппированными» элементами? - PullRequest
1 голос
/ 11 февраля 2020

Рассмотрим пример 4 цветов, упакованных в один и тот же вектор (этот аспект дизайна не может быть легко изменен - ​​например, от третьей стороны):

std::vector rgb_colors = {1,1,1,2,2,2,3,3,3,4,4,4};

Может работать следующее:

for (size_t ci = 0; ci + 2 < rgb_colors.size(); ci+=3) {
  auto& red_component = rgb_colors[ci];
  auto& green_component = rgb_colors[ci+1];
  auto& blue_component = rgb_colors[ci+2];
  //...
}

В вакууме этот метод достаточно «невинен». Однако с современным c ++ / et c. Я обычно избегаю этого метода кодирования, поскольку он более хрупкий / подверженный ошибкам / избыточный / et c. и теперь предпочитает диапазон, основанный на циклах, итераторах и т. д. c. .

Так что же это за выразительный или элегантный способ решить эту проблему?

Обновление: Добавлено примечание о что «расположение данных» не может быть легко изменено.

Ответы [ 2 ]

4 голосов
/ 11 февраля 2020

Как упоминалось в комментариях, создайте struct, представляющий цвет, или, скорее, используйте тот, который, вероятно, поставляется с любой библиотекой, которую вы используете для графики, чтобы вы могли правильно с ней взаимодействовать:

struct color {
    int red, green, blue;
    // Add members as you see fit
};

std::vector rgb_colors = {color{1,1,1}, color{2,2,2}, color{3,3,3}, color{4,4,4}};
// or
// std::vector<color> rgb_colors = {{1,1,1}, {2,2,2}, {3,3,3}, {4,4,4}};

for (auto& c : rgb_colors) {
  auto& red_component = c.red;
  auto& green_component = c.green;
  auto& blue_component = c.blue;
  //...
}

Или, не обращая особого внимания на конкретный пример, вы можете использовать адаптер диапазона / итератора, например boost::adaptors::stride:

#include <boost/range/adaptor/strided.hpp>

//...

for (auto& c : boost::adaptors::stride(rgb_colors, 3)) {
  auto& red_component = (&c)[0];
  auto& green_component = (&c)[1];
  auto& blue_component = (&c)[2];
  //...
}

Это все же (как в вашем примере) требует от вас убедиться, что длина вектора делится на 3, чтобы избежать UB и чтобы элементы были частью непрерывного массива (как обеспечивает std::vector), так что арифметика указателя c (&c)[i] имеет четкое определение поведение.

2 голосов
/ 11 февраля 2020

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

Но если вы по какой-то причине застряли в последовательности, где N элементов время от времени что-то значит, и вам нужно много с этим работать, вот как я бы использовал boost::iterator_adaptor для создания итератора, который одновременно захватывает N элементов.

#include <cstddef>
#include <utility>
#include <iterator>
#include <tuple>
#include <boost/iterator/iterator_adaptor.hpp>

namespace N_elements_iterator_detail {
    template <typename T, std::size_t N>
    using ignore_N = T;

    template <typename T, typename IndSeq>
    struct repeat_type_helper;
    template <typename T, std::size_t ...Inds>
    struct repeat_type_helper<T, std::index_sequence<Inds...>>
    { using type = std::tuple<ignore_N<T, Inds>...>; };

    template <typename T, std::size_t N>
    using repeat_type = typename repeat_type_helper<T, std::make_index_sequence<N>>::type;

    template <typename IndSeq>
    struct deref_helper;

    template <std::size_t ...Inds>
    struct deref_helper<std::index_sequence<Inds...>>
    {
        template <typename RetType, typename FwdIter>
        static RetType deref(FwdIter iter) {
            FwdIter iter_array[] =
                { (static_cast<void>(Inds), iter++) ... };
            return RetType( *iter_array[Inds]... );
        }
    };
}

template <typename FwdIter,
          typename std::iterator_traits<FwdIter>::difference_type N,
          std::enable_if_t<(N>0)>* = nullptr>
class N_elements_iterator :
    public boost::iterator_adaptor<
        N_elements_iterator<FwdIter, N>, // CRTP Derived type
        FwdIter,                         // Implementation base iterator
        N_elements_iterator_detail::repeat_type<
            typename std::iterator_traits<FwdIter>::value_type, N>,
        boost::use_default,              // Iterator category
        N_elements_iterator_detail::repeat_type<
            typename std::iterator_traits<FwdIter>::reference, N>>
{
public:
    using N_elements_iterator::iterator_adaptor::iterator_adaptor;

private:
    friend class boost::iterator_core_access;

    typename N_elements_iterator::reference dereference() const {
        return N_elements_iterator_detail::deref_helper<
            std::make_index_sequence<N>>
            ::template deref<typename N_elements_iterator::reference>(
                this->base());
    }

    void advance(typename N_elements_iterator::difference_type dist) {
        std::advance(this->base_reference(), N * dist);
    }

    void increment() { advance(1); }
    void decrement() { advance(-1); }

    // N must be the same, but we can subtract for example
    // N_elements_iterator<C::iterator, N> and N_elements_iterator<C::const_iterator, N>
    template <typename OtherIter>
    auto distance_to(const N_elements_iterator<OtherIter, N>& other) const {
        return N * std::distance(this->base(), other.base());
    }
};

template <auto N, typename FwdIter>
N_elements_iterator<FwdIter, N> make_N_elements_iterator(FwdIter iter)
{ return N_elements_iterator<FwdIter, N>{ iter }; }

// -----

#include <vector>
#include <iostream>

int main() {
    std::vector rgb_colors = {1,1,1,2,2,2,3,3,3,4,4,4};

    for (auto c = make_N_elements_iterator<3>(rgb_colors.cbegin());
         c != make_N_elements_iterator<3>(rgb_colors.cend());
         ++c) {
        auto&& [red, green, blue] = *c;
        std::cout << "red " << red
                  << ", green " << green
                  << ", blue " << blue
                  << '\n';
    }
}

См. эта программа на колиру .

...