HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Best syntax for executing an action between each loop iteration.

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
executingeachloopsyntaxactioniterationbetweenforbest

Problem

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)

Solution

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

template
void print(std::vector const &x) {
  std::cout ::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
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
void print(std::vector 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.

Code Snippets

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";
}
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";
}

Context

StackExchange Code Review Q#2225, answer score: 5

Revisions (0)

No revisions yet.