28

In C++11, one can use initializer lists to initialize parameters in functions. What is the purpose of it? Can't the same be done with const vectors? What is the difference of the two programs below?

Using an initializer list:

#include <iostream> using namespace std; int sumL(initializer_list<int> l){ int sum = 0; for (const auto i: l){ sum += i; } return sum; } int main(){ cout << sumL({1, 2, 3}) << "\n"; return 0; } 

Using a const vector:

#include <iostream> #include <vector> using namespace std; int sumV(const vector<int> l){ int sum = 0; for (const auto i: l){ sum += i; } return sum; } int main(){ cout << sumV({1, 2, 3}) << "\n"; return 0; } 
2
  • 2
    The second code requires more than needed (and the vector is constructed withe an initializer_list, too) Commented Jan 3, 2015 at 9:37
  • Offtopic: note that you make a copy of the vector, i.e., you didn't use a reference &. The same holds in the for loop, now it is just an int but with larger objects the reference will avoid that you copy each object in the vector. Commented Jan 3, 2015 at 10:11

6 Answers 6

21

The common use of std::initializer_list is as argument to constructors of container (and similar) classes, allowing convenient initialisation of those containers from a few objects of the same type. Of course, you can use std::initializer_list otherwise and then use the same {} syntax.

Since a std::initializer_list has a fixed size, it doesn't require dynamic allocation and hence can be efficiently implemented. A std::vector, on the other hand, requires dynamic memory allocation. Even in your simple example it is unlikely that the compiler will optimize this overhead away (avoid the intermediary std::vector and its dynamic memory allocation). Other than that, there is no difference in the outcome of your programs (though you should take a const std::vector<int>& argument to avoid a copy and its associated dynamic memory allocation).

Sign up to request clarification or add additional context in comments.

Comments

12

initializer_list uses optimal storage location and prevents unnecessary calls, it's designed to be lightweight, while with vector there's a heap allocation and there may be more copies/moves made.

Comments

7

The semantics of the two are quite different. The initializer_list has pointer semantics while the vector has value semantics.

In your first example, the compiler will generate code similar to the following:

int const __temp_array[3] = {1, 2, 3}; cout << sumL(std::initializer_list<int>(__temp_array, __temp_array + 3)) << "\n"; 

This is explained in [dcl.init.list]/5. As you can see, within sumL you have access to const pointers to the elements of the braced-init-list, this means you have no other option but to copy the elements out of the list.

In case of sumV you could've std::moved the elements from the vector if needed (assuming the parameter type is not const).

Similarly, copying the initializer_list performs shallow copies, i.e. only the pointers will be copied, while copying the vector, of course, means the elements will be copied.

In your example, none of the points above really make any difference, other than constructing the vector will require dynamic memory allocation, while constructing the initializer_list will not.

2 Comments

Thank you for details. Please clarify for me one thing. Am i right if say initializer_list<object> il1 = {...}; stores objects inside container (or somewhere, on stack?), and then its copy initializer_list<object> il2 = il1; stores links to objects so every copy of that ils will share the same objects?
7

initalizer_list is not a generalized container like std::vector. It's primary purpose is object initialization. If the low overheard and no heap allocation is attractive to you I'd suggest looking at std::array. It a fixed sized stack allocated array that has all the conveniences of a STL container, which is essentially a thin wrapper on top of a c-array.

Comments

2

I feel describing initializer_list as lightweight misses the point.

The initializer_list is (by necessity) handled by the compiler. Its definition does not include memory management or ownership, nor does it have a useful (non-blank) constructor, nor must it conform to any particular layout. The standard suggests its representation is basically pair of pointers (begin and end):

An object of type initializer_list provides access to an array of objects of type const E. [Note: A pair of pointers or a pointer plus a length would be obvious representations for initializer_list.]

And we can see elsewhere in the standard that initializer_list is generally used as if something had happened, but does not actually require anything concrete::

An object of type std::initializer_list is constructed from an initializer list as if the implementation generated and materialized a prvalue of type “array of N const E”

Allocation, initialisation and teardown of members, if required, is added by the compiler, and has nothing to do with the list itself.

Contrast this with a vector that can be modified on demand, performs memory management, and actually owns the objects inside the array, mandating that they exist in the heap.

As to which to use, it depends on the circumstances, in the example above I'd use the initializer list, since it would seem simpler. However I'd believe a vector could be more efficient if I were moving contents into my own vector (and I could therefore simply use std::move), or if I believed the user would likley already be starting out with their own vector, and I could therefore avoid an unnecessary intermediate.

Comments

0

Initializer lists

A lightweight array-like container of elements created using a "braced list" syntax. For example, { 1, 2, 3 } creates a sequences of integers, that has type std::initializer_list. Useful as a replacement to passing a vector of objects to a function.

 int sum(const std::initializer_list<int>& list) { int total = 0; for (auto& e : list) { total += e; } return total; } auto list = {1, 2, 3}; sum(list); // == 6 sum({1, 2, 3}); // == 6 sum({}); // == 0 

However, in your case if you will do it the way you are doing compiler likely to optimize it the same way as you can see the vector function implementation mentioned below.

template <class T> struct S { std::vector<T> v; S(std::initializer_list<T> l) : v(l) { std::cout << "constructed with a " << l.size() << "-element list\n"; } void append(std::initializer_list<T> l) { v.insert(v.end(), l.begin(), l.end()); } std::pair<const T*, std::size_t> c_arr() const { return {&v[0], v.size()}; // copy list-initialization in return statement // this is NOT a use of std::initializer_list } }; 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.