3
votes
\$\begingroup\$

The question of the cleanest way to write a loop that executes some action between each iteration has always interested me.

In a sense, what is the best way in c/c++ to implement this fictional construct:

for (...) {
} between {
}

Using such a construct you could trivially implement string join(): (pseudocode)

result = "";
foreach ( item : input ) {
    result += str(item);
} between {
    result += sep;
}

I have looked through some popular code libraries to see how these types of loops are implemented, and there are some common strategies:

  • move the "between" code into an "if (!first/last iteration)" inside the loop.
    • This is the go-to method when the index/iterator/result freely stores the notion of first/last iteration (such as checks for values of 0, .empty(), NULL etc).
  • transform the "loop body" into a function and call it from two places: before and during the loop, and change the loop to skip the first element. (minor code duplication)
    • This is the go-to method when the "loop body" is a single function call

Neither of these is a completely generalized solution, and while its only a few lines + a state variable, I'm trying to find an ideal solution.

Priorities:

  • No needless source code duplication (but willing to accept binary code duplication)
  • Efficiency
  • Clear semantics
  • Trivially usable (aka simple syntax, few library requirements)
\$\endgroup\$

locked by Jamal Jun 15 '14 at 6:16

This question exists because it has historical significance, but it is not considered a good, on-topic question for this site so please do not use it as evidence that you can ask similar questions here. This question and its answers are frozen and cannot be changed. See the help center for guidance on writing a good question.

Read more about locked posts here.

  • 4
    \$\begingroup\$ This seems more appropriate for Stack Overflow. \$\endgroup\$ – Fred Nurk May 4 '11 at 5:27
  • \$\begingroup\$ Though it doesn't generalize all situations, for the specific example you give, I'd use an infix_iterator: stackoverflow.com/questions/3496982/…. This is similar to your first option, except that the iterator and the iteration are separated from each other. \$\endgroup\$ – Jerry Coffin May 4 '11 at 21:46
5
votes
\$\begingroup\$

The closest I've come to this in C or C++ is a slight modification to Knuth's loop-and-a-half:

template<class T>
void print(std::vector<T> const &x) {
  std::cout << '[';
  typename std::vector<T>::const_iterator
    begin = x.begin(),
    end = x.end();
  if (begin != end) {  // Duplicated condition to handle the empty case.
    while (true) {  // Here is the "loop and a half".
      std::cout << *begin;
      if (++begin == end) break;
      std::cout << ", ";
    }
  }
  std::cout << "]\n";
}

C++0x allows you to generalize:

template<class Iter, class Body, class Between>
void foreach(Iter begin, Iter end, Body body, Between between) {
  if (begin != end) {  // This duplication doesn't matter as it is
                       // wrapped up in a library function.
    while (true) {
      body(*begin);
      if (++begin == end) break;
      between();
    }
  }
}

template<class T>
void print(std::vector<T> const &x) {
  std::cout << '[';
  foreach(x.begin(), x.end(),
    [](T const& v) {
      std::cout << v;
      // Long code here is still readable.
    },
    []{
      std::cout << ", ";
      // Long code here is still readable.
    });
  std::cout << "]\n";
}

Lambda capture even allows you to modify local variables in the function calling foreach.

\$\endgroup\$
  • \$\begingroup\$ I really like this solution, and like that you have provided a C++0x generalization (I am an avid user of C++0x though admittedly most are not). I would also add a Range overload to use c++0x range based for loop, and I would also add a version that accepts four lambdas "check" "increment" "body" "between". I am going to play with this solution today. \$\endgroup\$ – mmocny May 4 '11 at 14:57
-2
votes
\$\begingroup\$

Read about visitor pattern...

For more complex object, sample:

template<class T>
struct Walker
{
    Walker(Item *parent): m_parent(parent){}
    Walker(Item *parent, T const& f): m_parent(parent), f(f){}
    // visit all elements
    Walker& Walk()
    {
        TestClass::iterator it = parent->begin(), end = parent->end();
        for( ; it != end; ++it)
        {
            f.Visit(*it);
        }
        return *this;
    }
    // get visited result
    T const& GetF()const { return f; }
private:
    Walker& operator = (Walker const& rhs);
    Item* m_parent;
    T f;
};

and define class, for example:

struct VisitorClass
{
    void Visit(TestClass*){}
};

after that use it:

Walker<VisitorClass> testVisit(this);
\$\endgroup\$
  • 2
    \$\begingroup\$ I don't think this answers the question.. and I very well know about the visitor pattern and don't think your implementation is very well done.. \$\endgroup\$ – mmocny May 4 '11 at 15:00

Not the answer you're looking for? Browse other questions tagged or ask your own question.