C++ Via TFL: The Range-Based For Loop

C++ catches up with constructs common in Java and C# with the new range-based for loop.

catInfinity
Image source: normanack, Flickr

A simple example should suffice to demonstrate its common use.

TEST(ARangeBasedForLoop, CanIterateOverAVector) {
   vector collectedNumbers;
   vector numbers{1, 2, 3};

   for (int each: numbers)
      collectedNumbers.push_back(each);

   ASSERT_THAT(collectedNumbers, Eq(vector { 1, 2, 3}));
}

It’s always nice to know the appropriate names for the syntactical parts of things. Here’s an overly clever test that shows what things are called in the range-based for loop.

TEST(ARangeBasedForLoop, HasAppropriateNamesForItsSyntacticalParts) {
   typedef int type_specifier_seq;
   vector expression{1, 2, 3, };
   vector collectedNumbers;
   auto statement = [&] (int each) -> void { collectedNumbers.push_back(each); };

   for (type_specifier_seq simple_declarator: expression) statement(simple_declarator);

   ASSERT_THAT(collectedNumbers, Eq(vector { 1, 2, 3}));
}

The range-based for loop supports iterating over a number of common things. Here are a few.

TEST(ARangeBasedForLoop, CanIterateOverAnArray) {
   vector collectedNumbers;
   int numbers[]{1, 2, 3};

   for (int each: numbers)
      collectedNumbers.push_back(each);

   ASSERT_THAT(collectedNumbers, Eq(vector { 1, 2, 3}));
}

TEST(ARangeBasedForLoop, CanIterateOverAMap) {
   map dictionary{{1, "uno"}, {2, "dos"}};
   map collectedDictionary;

   for (pair pair: dictionary)
      collectedDictionary[pair.first] = pair.second;
   ASSERT_THAT(collectedDictionary, Eq(map{{1, "uno"}, {2, "dos"}}));
}

TEST(ARangeBasedForLoop, CanIterateOverAString) {
   vector collectedChars;

   for (char each: "abc")
      collectedChars.push_back(each);

   ASSERT_THAT(collectedChars, Eq(vector{ 'a', 'b', 'c', 0 }));
}

I’ve shown explicit type information in the prior examples. You should usually prefer auto, instead.

TEST(ARangeBasedForLoop, ShouldPreferAnInferencingTypeSpecifier) {
   vector collectedNumbers;

   for (auto each: {1, 2, 3})
      collectedNumbers.push_back(each);

   ASSERT_THAT(collectedNumbers, Eq(vector{ 1, 2, 3}));
}

If you want to update each element as you iterate…

TEST(ARangeBasedForLoop, CanAccessEachElementByReference) {
   vector strings{"a", "b"};

   for (auto& each: strings)
      each += each;

   ASSERT_THAT(strings, Eq(vector{ "aa", "bb" }));
}

Or if you want better performance, but want to disallow modifying each element.

TEST(ARangeBasedForLoop, CanAccessEachElementByConstReference) {
   vector strings{"a", "b"};
   vector collectedStrings;

   for (const auto& each: strings)
      // each += each; // fails compilation
      collectedStrings.push_back(each);
     

   ASSERT_THAT(strings, Eq(vector{ "a", "b" }));
}

The range-based for loop can iterate over anything that implements begin() and end() (each returning an iterator object–something that implements operator!=, operator*, and operator++). Here is a simple class that supports iteration across a range of numbers, (also supporting the ability to skip elements).

class IntSequence {
public:
   IntSequence(int start, int stop, int by=1): start_{start}, stop_{stop}, by_{by} {}

   class iterator {
   public:
      iterator(int value, int by) : current_(value), by_(by) {}

      bool operator!=(const iterator& rhs) const {
         return current_ <= rhs.current_;
      }

      int& operator*() { return current_; }

      iterator operator++() {
         current_ += by_;
         return *this;
      }
   private:
      int current_;
      int by_;
   };
   iterator begin() { return iterator(start_, by_); }
   iterator end() { return iterator(stop_, by_); }

private:
   int start_;
   int stop_;
   int by_;
};

And here’s an example demonstrating use:

TEST(ARangeBasedForLoop, CanIterateOverAnythingImplementingBeginAndEnd) {
   vector collectedNumbers;
   int start{3};
   int stop{10};
   int by{2};
   IntSequence sequence(start, stop, by);

   for (auto each: sequence)
      collectedNumbers.push_back(each);

   ASSERT_THAT(collectedNumbers, Eq(vector{3, 5, 7, 9}));
}

(This example could be construed as contrived: Why not simply use a regular ol’ for loop? Most of the time, that idiom is simple and probably preferred, but you might find value in the ability to pass around a sequence concept to other functions, or to serialize it, or otherwise use it where having an object abstraction might simplify code.)

Note: Code build using gcc 4.7.2 under Ubuntu.

This entry was posted in C++, programming, TDD, test-driven development, test-focused learning, TFL, unit testing. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>