Why use initializer_list instead of vector in parameters?
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?
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.
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.
The biggest advantage of initializer_list
over vector
is that it allows you to specify in-place a certain sequence of elements without requiring dedicate 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.