Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ conditional uni-directional iterator

Tags:

c++

c++11

I want to achieve something like the pseudo-code below:

string foo;  // or vector<int> foo;
auto itr = bar?  foo.begin() : foo.rbegin();
auto end = bar?  foo.end() : foo.rend();
for (  ; itr != end; ++itr) {
// SomeAction ...
}

That is, I want to set itr to be either a forward iterator or the reverse iterator, depending on some condition bar, to scan in forward or reverse direction.

Apparently code like that won't work, since forward iterator and reverse iterator has different type.

Note that I don't want to split into two loops, as those code like // SomeAction will be duplicated.

How can I do that? Answers using C++11 and/or before are preferred.

Also, please elaborate if string and vector have different solutions.

like image 886
Robin Hsu Avatar asked May 14 '19 15:05

Robin Hsu


3 Answers

I would put the logic in a two-iterator function:

<template typename Iter>
void do_stuff(Iter first, Iter last)
{
    for(; first != last; ++first)
    {
        // Do logic
    }
}

bar ? do_stuff(foo.begin(), foo.end()) : do_stuff(foo.rbegin(), foo.rend());
like image 105
Mark B Avatar answered Oct 21 '22 19:10

Mark B


The forward and reverse iterators are different types for most if not all containers, so unfortunately if it is a runtime decision, they can not simply be assigned to the same variable through the use of auto.

One option is to move the use of them into a template function:

template<class Iterator> void loop(Iterator begin, Iterator end)
{
    for (auto itr = begin; itr != end; ++itr) { ... }
}

if (bar) loop(foo.begin(), foo.end());
else loop(foo.rbegin(), foo.rend());

In newer versions of C++ (C++14 and newer, so not C++11) the loop function can be a lambda, by using auto as the parameter type.

auto loop = [](auto begin, auto end)
{
    for (auto itr = begin; itr != end; ++itr) { ... }
};

Another option, although somewhat more involved would be to make a wrapper type that can contain either an iterator or reverse iterator, and acts like an iterator itself with at least the comparison, increment and dereference operators.

like image 17
Fire Lancer Avatar answered Oct 21 '22 20:10

Fire Lancer


I don't want to split into two loops, as those code like // SomeAction will be duplicated.

Put the action into a lambda.

auto lambda = [&](char &val) // `ElementType &`
{
    // ...
};

if (bar)
{
    for (auto &val : foo)
        lambda(val);
}
else
{
    for (auto it = foo.rbegin(); it != foo.rend(); it++)
        lambda(*it);
}

Alternatively, use indices rather than iterators. This will only work with containers that allow random access.

std::size_t i, end, step;
if (bar)
{
    i = 0;
    end = foo.size();
    step = 1;
}
else
{
    i = foo.size() - 1;
    end = -1;
    step = -1;
}

for (; i != end; i += step)
{
    // ...
}

like image 9
HolyBlackCat Avatar answered Oct 21 '22 21:10

HolyBlackCat