C++ 11: Range based for loop and std::for_each() function

Discussion in 'C++' started by BiplabKamal, Apr 30, 2016.

  1. C++11 has introduced a lot of of features to make programming easier. For example automatic type deduction and less typing. Range based for loop and std::for_each() functions are among those. They are new techniques for iterating through the elements of a sequence container, like vector, arrays or strings. Before C++ 11 we used normal for loop or while loop to access the elements of a collection in a container or array. For example if you want to access each element of a vector sequentially using traditional for loop or while loop, the code looks like:

    Code:
    #include<iostream>
    #include<vector>
    using namespace std;
    int main() 
    {
        vector<int> v{ 1,2,3,4,5,6,7,8,9,10 };
        // update elements for loop using iterator
        for (auto _begin = v.begin(), _end = v.end();_begin != _end;++_begin)
        {
            int& item = *_begin;
            item *= item; // replace the value with it’s square
        }
        //while loop using iterator
        auto _begin = v.begin(), _end = v.end();
        while (_begin != _end)
        {
            cout <<*_begin << " ";
            ++_begin;
    
        }
        return 0;
    }
    
    Now you can use new standard library function std::for_each() to do the same thing. Here is the code:
    Code:
    #include<iostream>
    #include<vector>
    #include<algorithm>
    using namespace std;
    int main() 
    {
        vector<int> v{ 1,2,3,4,5,6,7,8,9,10 };
        // using std::for_each() function
        std::for_each(v.begin(), v.end(), [](int& item) {item *= item;}); // Update the elements
        std::for_each(v.begin(), v.end(), [](int item) {cout << item << " ";});//Display the elements
            return 0;
    }
    
    To use the std::for_each() you need to include header <algorithm>.

    Now another C++11 feature called range based for loop will implement the same functionality in the following code

    Code:
    #include<iostream>
    #include<vector>
    using namespace std;
    int main() 
    {
        vector<int> v{ 1,2,3,4,5,6,7,8,9,10 };
        // Update items
        for (int& item:v)
        {
            item *= item;
        }
        //Display items
        for (int item : v)
        {
            cout << item << " ";
        }
        return 0;
    }
    
    In the first example we used a for loop and while loop to access elements of the container. for loop and while loops are general purpose loops and can be used for any repeating task. These loops will be always there. But there has been a growing use of STL sequence containers and most of the time you will be implementing the logic of inspecting through each element in sequence and even modify them or create summary report of the collection. These created a need for easier way to iterate through the elements sequentially. Range based for loop and std::for_each() function in C++11 are fulfilling that need in different situation. These features are tightly coupled with collection type object and mostly used with STL sequential containers, though can be used for user defined class meeting the requirement to be sequential container. We will see how that is possible later. std::for_each() and range based for loop behave similar way except few differences. In the above example I am sure you will not disagree that the version using range based for loop is much simpler and easier to write though internally they are almost same. But there are other scenarios when each one has advantage. First we will discuss their syntax and usage. Later we will compare them.

    std::for_each()



    std::for_each() is a template function which takes the first and last iterator as the input for the range and a unary function which will be applied on each element of the sequence container. The prototype of the function looks like:
    Code:
    template <class InputItr, class Functor>
    
    Functor for_each(InputItr begin, InputItr end, Functor fnobj);
    
    where InputIter is a type which meets the requirement of an InputIterator concept and Functor is a type of callable object like std::function, lamda expression or a functor. Functor should be moveconstructible and match the signature of the following functions:
    Code:
    void fun(const Type &a); 
    or
    void fun(Type a); 
    
    where the type Type should be such that an object of InputItr can be dereferenced and implicitly converted to Type.

    The possible implementation of std::for_each() is :
    Code:
    template <class InputIterator, class Functor>
    Functor for_each(InputIterator _begin, InputIterator _end, Functor fn)
    {
        while (_begin != _end)
        {
            fn(*_begin);
            ++_begin;
        }
        return std::move(fn);
    }
    
    std::for_each() returns the functor it applied on the elements. Here is an example: Suppose you have a std::map object of employees with employee id as key and employee name as the value. Now you want to get the list of the employees having employee id with a range. Following code will demonstrate the use of std::for_each() function

    Code:
    #include<iostream>
    #include<algorithm>
    #include<map>
    #include<list>
    
    using namespace std;
    void GetEmployees(std::map<int, string> employees, int first, int last, list<string>& lst)
    {
        for_each(employees.find(first), employees.find(last + 1), [&lst](std::pair<int, string> item) {lst.emplace_back(item.second);});
    }
    int main() 
    {
        std::map<int, string> employees{ {1,"Name1"},{2,"Name2"},{ 3,"Name3" },{ 4,"Name4" },{ 5,"Name5" },{ 6,"Name6" } };
        list<string> names;
        GetEmployees(employees,3,5,names); // Get the employee names for id 2, 4,and 5
        for (string s : names)
            cout << s.c_str() << " ";
        cout << endl;
        return 0;
    }
    

    Range based for loop



    Range based for loop is the simplest form of iteration loop. This loop iterates through the sequence collection and retrieve the current element for processing. It process all the elements of the collection but the loop can be exited or continued using break and continue statement in side the loop. The syntax looks like:
    Code:
    for (Type val : sequence_object) loop_statement 
    or
    for (Type& val : sequence_object) loop_statement
    
    where sequence_object is a sequence container object and Type is the type of the element in the sequence_object. Internally it works similar to a normal for loop like:
    Code:
    for ( InputIterator _begin = sequence_object.begin(), _end = sequence_object.end();_begin != _end; ++_begin)
    {
        Type val = *_begin;
        loop_statement
    }
    
    Here is an example code where range base for loop is used to access each character of a std::string object.
    Code:
    #include<iostream>
    using namespace std;
    int main() 
    {
        string S{ "abcdefghijklmnopqrstuvwxyz" };
        //Display each character seperately
        for (char c : S)
            cout << c << " ";
        return 0;
    }
    
    So far we have used sequence containers available in STL which support begin() and end method and iterators are defined for them. But what about collections which does not support begin(), end() or iterator? For example you can use range based for loop for C style array also as shown below
    Code:
    int arr[10]{ 1,2,3,4,5,6,7,8,9,10 };
    for (int i : arr)
        std::cout << i << " ";
    
    How is it happening? It is possible by using template functions std::begin(array) and std::end(array) which take an array and return iterators. The expanded array will look like:
    Code:
    for (auto _begin = std::begin(arr), _end = std::end(arr);_begin != _end; ++_begin)
    {
        int i = *_begin;
        std::cout << i << " ";
    }
    
    From above examples it is clear that if you want to use your container class with range based for loop you have to implement an iterator class along with begin and end methods which will return the iterator object and it should be move constructible. Also the iterator class must implement prefix operator++, operator!= and operator*. Here is an example of a user defined class that support range based loop as well as std::for_each() function

    Code:
    #include<iostream>
    
    #include<memory>
    #include<algorithm>
    
    using namespace std;
    
    template<class T>
    class MyItr
    {
    private:
        int index;
        T* theArray;
    public:
        MyItr(T* arr, int pos) :theArray{ arr }, index{ pos }
        {
            cout << endl << "Iterator Constructor is called" << endl;
        }
        
        T& operator *() const
        {
            cout << endl << "* operator is called" << endl;
            return theArray[index];
        }
        
        const MyItr& operator++() // Prefix incrementor
        {
            cout <<endl<< "Prefix increment operator is called" << endl;
            ++index;
            return *this;
        }
        const MyItr& operator+(int pos) // + opearator
        {
            cout << endl << "+ operator is called" << endl;
            index+=pos;
            return *this;
        }
        const MyItr& operator-(int pos) // - operator
        {
            cout << endl << "- operator is called" << endl;
            index -= pos;
            return *this;
        }
        
        bool operator !=(const MyItr& other) const
        {
            cout << endl << "!= operator is called" << endl;
            return index != other.index;
        }
    };
    
    template<class T>
    class MyArray
    {
    
    public:
        
        MyArray(int csize) :size{ csize } // All elements is set to zero
        {
            pArr = unique_ptr<int>(new T[size]);
        }
        
        MyItr<T> begin()
        {
            cout << endl << "Creating begin iterator" << endl;
            return MyItr<T>(pArr.get(), 0);
        }
        
        MyItr<T> end()
        {
            cout << endl << "Creating end iterator" << endl;
            return MyItr<int>(pArr.get(), size);
        }
        
    
    private:
        const int size = 5; //Size of the array
        unique_ptr<T> pArr;
    
    };
    
    struct Sum {
        Sum() :sum{ 0 } {}
        void operator()(int i) { cout << i << " ";sum += i; }
        int sum;
    };
    
    int main() 
    {
        //vector<int>::const_iterator itr;
        MyArray<int> arr(10);
        int i{ 1 };
        // replace each number with it's aquare number
        for (int& item : arr)
        {
            item = i*i;
            ++i;
        }
        //Find the sum of a range 
        Sum s = std::for_each(arr.begin()+3, arr.end()-3, Sum());
        cout << endl << "Sum = " << s.sum<<endl;
        
        return 0;
    }
    

    Comparison



    Normal loop and range based loop has it’s own utility. Normal loop is a general purpose loop but range based for loop and std::for_each() function is meant for specific looping scenario. Ultimately they all work like a normal loop internally, hence performance is same. Range based for loop and std:for_each both are expanded to a normal for loop internally. But for ease of programming each of them are better equipped in their respective uses. For example range based loop is very easy to write for sequence containers when you want to iterate through all the elements and loop statement is similar to a normal for loop. In this case inside the loop statement you are free to do any thing: modify the element, get the element, exit from the loop, go to next iteration (continue) or do some other activity.

    On the other hand std_foreach() is handy when you want to process the elements in the container withing a sub range of the whole elements. In this case you cannot have break or continue statement. One advantage is that if your multiple loops are using the same code to process the elements then you can reuse the code by passing the function object.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice