Show Lecture.InitializerLists as a slide show.
CS253 Initializer Lists
Initialization
Consider this code:
vector<int> v = {11,22,33,44,55};
set<int> s = {11,22,33,44,55};
- What sort of thing is
{11,22,33,44,55}
?
- What type is it?
- No, not a flowery description—a C++ type.
- Are the two instances different types?
{11,22,33,44,55}
is an object of the type
std::initializer_list<int>
.
- The compiler figures it out, same way it decides that
'X'
is a char.
- It’s a real class, a container, with the traditional types & methods:
iterator
.size()
.begin()
/ .end()
- There is no
.empty()
, for are no empty initializer lists.
- If it were empty, what type would it be?
initializer_list<
what? >
- It’s implemented using an array, with
contiguous storage,
so the iterator is simply a const pointer.
The name
Why is the name so god-awful long?
- You think of a better name.
- We’re introducing a new word to the standard.
- If you call it, say,
init
, that breaks many
user programs that already use init
.
- That’s why these names are sometimes unwieldy—to maximize
backward compatibility (i.e., enrage the fewest users).
Standalone
Since an initializer_list has iterators, .begin()
, and .end()
,
we can use it in a for loop:
for (auto v : {11,22,33,44,55})
cout << v << ' ';
11 22 33 44 55
or, more explicitly:
auto il = {11,22,33,44,55};
auto it = il.begin() + 3;
cout << *it << '\n';
44
or, even:
auto il = {11,22,33,44,55};
cout << *(il.begin()+3) << '\n';
44
Example
This can be used to avoid an array or vector:
string days[] = {"Mo","Tu","We","Th","Fr","Sa","Su"};
for (auto dow : days)
cout << dow << "·";
Mo·Tu·We·Th·Fr·Sa·Su·
for (auto dow : {"Mo","Tu","We","Th","Fr","Sa","Su"})
cout << dow << "·";
Mo·Tu·We·Th·Fr·Sa·Su·
Interaction with standard containers
So, how does this work?
unordered_set<int> us = {345,678,901,234,567,890};
for (auto v : us)
cout << v << ' ';
890 567 234 901 678 345
- The first line must be a ctor, since we are creating
us
.
- What is the declaration of that ctor?
unordered_set(const initializer_list<value_type> &);
- As you recall,
value_type
is a typedef for the
type being stored by the vector.
- Similarly, the assignment operator must be:
unordered_set<value_type> &operator=(const initializer_list<value_type> &)
Not Free
- To use
{ … }
for your container, you must write methods:
- It doesn’t just come for free.
Example
class Hundred {
int data[100];
public:
int &operator[](int n) { return data[n]; }
};
Hundred h;
h[1] = 123;
h[4] = 456;
cout << h[1]+h[4] << '\n';
579
Example that fails
class Hundred {
int data[100];
public:
int &operator[](int n) { return data[n]; }
};
Hundred h = {11,22,33,44,55};
cout << h[1]+h[4] << '\n';
c.cc:7: error: could not convert '{11, 22, 33, 44, 55}' from '<brace-enclosed
initializer list>' to 'main()::Hundred'
Example
class Hundred {
int data[100];
public:
Hundred(const initializer_list<int> &il) {
copy(il.begin(), il.end(), data);
}
int &operator[](int n) { return data[n]; }
};
Hundred h = {11,22,33,44,55};
cout << h[1]+h[4] << '\n';
77
Example
class Hundred {
int data[100];
public:
Hundred(const initializer_list<int> &il) {
copy(il.begin(), il.end(), data);
}
int &operator[](int n) { return data[n]; }
};
Hundred h = {11,22,33,44,55};
cout << h[1]+h[4] << '\n';
h = {6,7,8,9};
cout << h[2] << '\n';
77
8
How did that work?
Example
class Hundred {
int data[100];
public:
Hundred(const initializer_list<int> &il) {
*this = il;
}
Hundred &operator=(const initializer_list<int> &il) {
copy(il.begin(), il.end(), data);
return *this;
}
int &operator[](int n) { return data[n]; }
};
Hundred h = {11,22,33,44,55};
cout << h[1]+h[4] << '\n';
h = {6,7,8,9};
cout << h[2] << '\n';
77
8
That’s better.
Don’t be fooled
Of course, sometimes, a brace is just a brace:
int a[5] = {11, 22, 33}; // initializer_list<int>?
cout << a[0] + a[2] << '\n';
44
Or, a more brutal example:
cout << {11,22,33}.size() << '\n';
c.cc:1: error: expected primary-expression before '{' token
Somehow, the compiler figures it out.
Introspection on the cheap
Remember the cheap way to learn the type of anything:
auto a = {1,2}; throw a;
terminate called after throwing an instance of 'std::initializer_list<int>'
SIGABRT: Aborted
size_t b = 1; throw b;
terminate called after throwing an instance of 'unsigned long'
SIGABRT: Aborted
throw "c";
terminate called after throwing an instance of 'char const*'
SIGABRT: Aborted