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

by Jeff Langr

February 19, 2013

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

Image source: normanack, Flickr

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

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.

Share your comment

Jeff Langr

About the Author

Jeff Langr has been building software for 40 years and writing about it heavily for 20. You can find out more about Jeff, learn from the many helpful articles and books he's written, or read one of his 1000+ combined blog (including Agile in a Flash) and public posts.