55

What is the actual benefit and purpose of initializer_list, for unknown number of parameters? Why not just use vector and be done with it?

In fact, it sounds like just a vector with another name. Why bother?

The only "benefit" I see of initializer_list is that it has const elements, but that doesn't seem to be a reason enough to invent this whole new type. (You can just use a const vector after all.)

So, what am I mising?

7
  • 1
    do you have one specific situation in mind? ("just use a vector and be done with it"... with what?). in order to use a vector of elements you first have to fill it up by repeated calls to push_back, while an initializer_list allows you to specify a list of elements directly in place. Commented Jan 19, 2013 at 13:30
  • @AndyProwl std::vector<std::string> v = {"foo", "bar"}; works fine. Commented Jan 19, 2013 at 13:39
  • 11
    Note that you probably should not impersonate another person. If your real name happens to be "Dennis Ritchie" (and you are not the co-inventor of the C language, which I suspect is the case because dmr has access to the C++ language standardization committee), you might want to change it to something like "Dennis Ritchie (not the famous one)". Commented Jan 19, 2013 at 13:40
  • 5
    @DennisRitchie: sure it does, that expression is using an initializer_list to initialize the vector Commented Jan 19, 2013 at 13:41
  • 3
    @DennisRitchie: A decade or so from now on, no one is going to remember the exact date/year when Dennis Ritchie(the famous one)passed away but your comments and posts on the forum will remain active and visible and future users might mistake them coming from Dennis Ritchie(the famous one). Its not the present but the future that might be affected. Commented Jan 19, 2013 at 14:23

5 Answers 5

58

It is a sort of contract between the programmer and the compiler. The programmer says {1,2,3,4}, and the compiler creates an object of type initializer_list<int> out of it, containing the same sequence of elements in it. This contract is a requirement imposed by the language specification on the compiler implementation.

That means, it is not the programmer who creates manually such an object but it is the compiler which creates the object, and pass that object to function which takes initializer_list<int> as argument.

The std::vector implementation takes advantage of this contract, and therefore it defines a constructor which takes initializer_list<T> as argument, so that it could initialize itself with the elements in the initializer-list.

Now suppose for a while that the std::vector doesn't have any constructor that takes std::initializer_list<T> as argument, then you would get this:

 void f(std::initializer_list<int> const &items);
 void g(std::vector<int> const &items);

 f({1,2,3,4}); //okay
 g({1,2,3,4}); //error (as per the assumption)

As per the assumption, since std::vector doesn't have constructor that takes std::initializer_list<T> as argument, which implies you cannot pass {1,2,3,4} as argument to g() as shown above, because the compiler cannot create an instance of std::vector out of the expression {1,2,3,4} directly. It is because no such contract is ever made between programmer and the compiler, and imposed by the language. It is through std::initializer_list, the std::vector is able to create itself out of expression {1,2,3,4}.

Now you will understand that std::initializer_list can be used wherever you need an expression of the form of {value1, value2, ...., valueN}. It is why other containers from the Standard library also define constructor that takes std::initializer_list as argument. In this way, no container depends on any other container for construction from expressions of the form of {value1, value2, ...., valueN}.

Hope that helps.

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

6 Comments

Just a question to your f() and g() function, does the const as parameter make the initializer list and the vector a low level const (which would be useless for inializer list)? Or does it just define the reference as const?
I dont understand what you mean by "low level const". If you mean, you cannot modify the actual object (being referred to by the reference) using the reference, then yes, you're right.
well theres a difference in different const's: const int *pI = &other; declares pI as a pointer to a const int (low-level-const) whereas int *const pI = &other; declares pI as a const pointer to a nonconst int (top-levl const). So why are you putting the const after the type? does it make a difference to const std::initializer_list<int> &items?
@NiklasVest: X const & and const X & are the same thing. Search on this site, you'll get too many topics discussing these things.
This answer explains how vector depends on initializer_list, but doesn't say why we should therefore use initializer_list. I am downvoting on these grounds. Other answers actually attempt to explain the "why," and I'm upvoting them.
|
26

Well, std::vector has to use initializer_list to get that syntax as it obviously can't use itself.

Anyway, initializer_list is intended to be extremely lightweight. It can use an optimal storage location and prevent unnecessary copies. With vector, you're always going to get a heap allocation and have a good chance of getting more copies/moves than you want.

Also, the syntax has obvious differences. One such thing is template type deduction:

struct foo {
    template<typename T>  
    foo(std::initializer_list<T>) {}
};

foo x{1,2,3}; // works

vector wouldn't work here.

2 Comments

Of all reasons, the major is what Paul mentions about the heap. Dynamic allocations make STL containers extremely inefficient, to the point of being unusable in certain systems, forcing you to write custom allocators for them in such instances. Init-lists to the rescue.
Why not show the obvious syntax differences? You state there are many, but then show just one--or a HALF of one, as you simply say vector wouldn't work but don't explain why not.
14

The biggest advantage of initializer_list over vector is that it allows you to specify in-place a certain sequence of elements without requiring delicate processing to create that list.

This saves you from setting up several calls to push_back (or a for cycle) for initializing a vector even though you know exactly which elements are going to be pushed into the vector.

In fact, vector itself has a constructor accepting an initializer_list for more convenient initialization. I would say the two containers are complementary.

// v is constructed by passing an initializer_list in input
std::vector<std::string> v = {"hello", "cruel", "world"};

Of course it is important to be aware of the fact that initializer_list does have some limitations (narrowing conversions are not allowed) which may make it inappropriate or impossible to use in some cases.

1 Comment

sorry Andy but I'm downvoting as your answer doesn't explain why I benefit from a function explicitly taking initializer_list as an arg instead of vector.
0

Sometimes all you require is an array of values (instead of a vector, say), but you also need to know how long the array is. Passing the type as initializer_list sure makes it easy to pass the length:

void g(const int arr[], size_t n);  // outside of my control

void f(initialize_list<int> values) {  // test code under my control
  const int* const arr = values.begin();
  const int n = values.size();
  g(arr, n);  // why I need to know arr len
  // ...
}

Then you can say:

f({1,2,3});  // some static test case

Any other alternative declaration of f will cause some pain:

  • if you were to declare function f to accept a normal array as the parameter type, you'd have to pass both the array (pointer) and its length, either dynamically as an additional parameter (as in function g), or statically by making function f a template
  • if you were to declare the parameter as a vector, then you'd be dynamically constructing a separate object merely to know the number of items, when you already know the length statically

To reiterate the point: sometimes you have a generic set of values that you need to pass to a function, and it's convenient to pass these values using std::initializer_list, since this allows the programmer to preserve information (the cardinality of the set) known at the time of compilation.

To be sure, passing the set of values as a std::vector is more general, but sometimes you don't need that full generality.

Comments

0

I would like to point out one drawback of std::initializer_list in comparison with std::vector.

Suppose we have the function f with std::initializer_list parameter. This function can accept variable number of elements (call f ({"cat", "dog", "parrot"}); in code below) - this is OK.

Some day, we may want this function to process elements not only from braced-enclosed initializer list, but also from some container (see vector vec in code below). Then, as far as I know, there is no (no easy?) way to do this (see here).

#include <initializer_list>
#include <iostream>
#include <string>
#include <vector>

void f (std::initializer_list<std::string> input_list)
{
    for (const auto & current_element : input_list)
        std::cout << current_element << std::endl;
}

int main()
{
    f ({"cat", "dog", "parrot"});

    std::vector<std::string> vec {"cow", "horse", "pig"};
    //No way (no simple way?) to pass elements from vec to f

    return 0;
}

At the same time, if we create function g with const std::vector<std::string> & parameter, it is capable of accepting both braced-enclosed initializer lists and elements from containers.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.