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\$
  • 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.