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

Перечисление в Python-подобном цикле в С++

Возможный дубликат:
Найти позицию элемента в цикле С++ 11 для цикла?

У меня есть vector, и я бы хотел его итератировать и в то же время иметь доступ к индексам для каждого отдельного элемента (мне нужно передать как элемент, так и его индекс в функцию). Я рассмотрел следующие два решения:

std::vector<int> v = { 10, 20, 30 };

// Solution 1
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx)
    foo(v[idx], idx);

// Solution 2
for (auto it = v.begin(); it != v.end(); ++it)
    foo(*it, it - v.begin());

Мне было интересно, может ли быть более компактное решение. Что-то похожее на Python enumerate. Это самое близкое к тому, что я использовал цикл диапазона С++ 11, но определение индекса за пределами цикла в частной области определенно похоже на худшее решение, чем 1 или 2:

{
    int idx = 0;
    for (auto& elem : v)
        foo(elem, idx++);
}

Есть ли какой-либо способ (возможно, использование Boost), чтобы упростить последний пример таким образом, чтобы индекс попадал в замкнутый цикл?

4b9b3361

Ответ 1

Как говорит @Kos, это такая простая вещь, что я действительно не вижу необходимости в дальнейшем ее упрощать и лично буду придерживаться традиционного цикла с индексами, за исключением того, что я бы выбрал std::vector<T>::size_type и просто используйте std::size_t:

for(std::size_t i = 0; i < v.size(); ++i)
    foo(v[i], i);

Я не слишком увлекаюсь решением 2. Он требует (вроде бы скрытых) итераторов произвольного доступа, которые не позволят вам легко менять контейнер, что является одной из сильных сторон итераторов. Если вы хотите использовать итераторы и сделать его общим (и, возможно, получить удар производительности, когда итераторы не являются произвольным доступом), я бы рекомендовал использовать std::distance:

for(auto it(v.begin()); it != v.end(); ++it)
    foo(*it, std::distance(it, v.begin());

Ответ 2

Вот какое-то смешное решение, использующее ленивую оценку. Сначала создайте объект-генератор enumerate_object:

template<typename Iterable>
class enumerate_object
{
    private:
        Iterable _iter;
        std::size_t _size;
        decltype(std::begin(_iter)) _begin;
        const decltype(std::end(_iter)) _end;

    public:
        enumerate_object(Iterable iter):
            _iter(iter),
            _size(0),
            _begin(std::begin(iter)),
            _end(std::end(iter))
        {}

        const enumerate_object& begin() const { return *this; }
        const enumerate_object& end()   const { return *this; }

        bool operator!=(const enumerate_object&) const
        {
            return _begin != _end;
        }

        void operator++()
        {
            ++_begin;
            ++_size;
        }

        auto operator*() const
            -> std::pair<std::size_t, decltype(*_begin)>
        {
            return { _size, *_begin };
        }
};

Затем создайте функцию функции-обертки, которая выведет аргументы шаблона и вернет генератор:

template<typename Iterable>
auto enumerate(Iterable&& iter)
    -> enumerate_object<Iterable>
{
    return { std::forward<Iterable>(iter) };
}

Теперь вы можете использовать свою функцию следующим образом:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& a: enumerate(vec)) {
        size_t index = std::get<0>(a);
        double& value = std::get<1>(a);

        value += index;
    }
}

Реализация выше - простая игрушка: она должна работать как с ссылками const, так и с const lvalue-ссылками, а также с rvalue-ссылками, но имеет реальную стоимость для последних, учитывая, что она копирует целой итерируемый объект несколько раз. Эта проблема, несомненно, может быть решена с помощью дополнительных настроек.

Так как С++ 17, объявления декомпозиции даже позволяют вам иметь классный синтаксис типа Python для указания индекса и значения непосредственно в инициализаторе for:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& [index, value] a: enumerate(vec)) {
        value += index;
    }
}

У меня нет совместимого с С++ 17 компилятора, чтобы проверить его, но я надеюсь, что auto&& в разложении сможет вывести index как std::size_t и value как double&.

Ответ 3

Один из способов - обернуть цикл в свою собственную функцию.

#include <iostream>
#include <vector>
#include <string>

template<typename T, typename F>
void mapWithIndex(std::vector<T> vec, F fun) {
   for(int i = 0; i < vec.size(); i++) 
       fun(vec[i], i); 
}

int main() {
   std::vector<std::string> vec = {"hello", "cup", "of", "tea"};
   mapWithIndex(vec, [](std::string s, int i){
      std::cout << i << " " << s << '\n';
   } );
}