Thursday 10 November 2011

C++11: Initializer lists and range-for statements

In my previous post I wrote about the auto keyword, using it as a return type and the decltype operator.  Hopefully you've had a chance to use these and hopefully you've been finding them incredibly useful.  I said that in my next post I would look at initializer lists and range-for statements, so let's get stuck in.

Initializer Lists
Perhaps one of the most annoying things I tend to have to do is create an array or vector and initialize it with some known values, if it's held in configuration then it's not too bad but I still end up having to do it sometimes.  Previously if you've wanted to just use an array this has been fairly trivial, we'd just write the following:

int a[] = { 1, 2, 3 };

But if we wanted to use a vector then we'd end up with 4 lines of code to do the same job:

vector<int> a;
a.push_back(1);
a.push_back(2);
a.push_back(3);

Which isn't particularly nice to write and can lead to RSI related injuries.  But now functions (including constructors which are referred to as an initializer-list constructor) can accept a {} list by accepting an argument with the type std::initializer_list<T>.  This has been pushed into the STL so our favourite containers should now accept a {} list for initialization.

vector<int> a = { 1, 2, 3 };

map<int, vector<string>> c({ 
    {1, { "Ignoring", "The", "Voices" } },
    {2, { "In", "My", "Head" } }
});

Doesn't that just look a lot better, and it's certainly easier to type.  The nice thing about this new type is that it means we can write our own functions which take initializer lists, whether we're creating our own container class or just writing a function which can accept a {} list of values.


template<class T> void MyFunction(initializer_list<T> values)
{
    cout << "Number of items in initializer list: " << values.size() << endl;
    for (auto i = begin(values); i < end(values); ++i)
    {
        cout << *i << " ";
    }
    cout << endl;
}

This method then works by simply calling it in the following manner:

MyFunction<int>({ 1, 2, 3 });

Now you may have noticed something different with the for loop in that method, instead of using "values.begin()" and "values.end()" it's using "begin(values)" and "end(values)".  These are two stand-alone methods which return iterators to the beginning and end of the of the collection; the nice thing about these methods is that they work on any structure which works in a similar way to STL iterators (i.e. implements operator++, operator!= and operator*), which means that they won't work on dynamic arrays.

Full example Code

Range-For Statements

If you're use to working in languages such as C# or Python then the chances are you're use to seeing statements like these:

C#: foreach (int i in my_list) { ... }
Python: for i in my_list: ...

These are statements which  provide a simple syntax for working with each item in an iterable structure.  To perform something similar in C++ we would write something more like this:

for (vector<int>::iterator it = my_list.begin(); it != my_list.end(); ++it) { ... }

Which works and it does what we want it to, but secretly we've been looking over the shoulders of the C#, Java etc... developers and coveting their range loops.  Well not any more, now we too have a range loop which works on any iterable structure (i.e. anything you can iterate through like an STL-sequence defined by a begin() and end(), [1]), including initializer lists.

for (auto i : my_list) { ... }

So to give a more complete example, and using what we covered earlier we can do the following:

vector<string> a = { "Ignoring", "The", "Voices" };
for (const auto s : a)
{
    cout << s << endl;
}

Full example code

Which just looks a whole lot different from the following which we would have needed to write before hand to accomplish the same thing.

vector<string> a;
a.push_back("Ignoring");
a.push_back("The");
a.push_back("Voices");

for (vector<string>::const_iterator it = a.begin(); it != a.end(); ++it)
{
    cout << *it << endl;
}

The next post I'm planning on doing is about lambda expressions, these are another fantastic language feature which I use a lot in other languages such as C# and Python so I'm glad that they've finally made their way into C++ as well.  As it's a fairly sizable topic by itself I'll probably just a do a single post on those and then single posts for other features as well.  I think that the items I've covered in this post and my last are really the easiest to get going with and which have a fairly large impact on the code we write daily.

References
[1] Bjarne Stroustrup C++11 FAQ

All code provided in this article is provided under a BSD license.  If you spot an error then please do let me know so that we can make this better for anyone else reading it.
Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

3 comments:

  1. Great post! No code corrections, just one grammar tweak: "now we to have a range loop" should be "now we too have a range loop".

    ReplyDelete
    Replies
    1. Thanks, glad you found it useful :) I've corrected the "deliberate" grammatical mistake now as well.

      Delete
  2. One slight addition, you can cheat pre-c++11:

    int arr[] = {1,2,3,4,5};
    vector ints(arr,arr+(sizeof(arr)/sizeof(arr[0])));
    -or simply-
    vector ints(arr,arr+5);

    Not pretty but it'll work.

    ReplyDelete