Reputation: 13
In Stroustrup's book, "Programming: Principles and Practices of Programming Using C++ (Second Edition)", the author creates three vectors as followed in their own struct:
struct Day {
vector<double> hour{ vector<double>(24,not_a_reading) };
};
struct Month { // a month of temperature readings
int month{ not_a_month }; // [0:11] January is 0
vector<Day> day{ 32 }; // [1:31] one vector of readings per day
};
struct Year { // a year of temperature readings, organized by month
int year; // positive == A.D.
vector<Month> month{ 12 }; // [0:11] January is 0
};
My question is, why does vector<Day> day{32};
create a vector of type Day with a size of 32, whereas vector<int>{32};
creates a vector of size 1?
Upvotes: 1
Views: 3749
Reputation: 96043
The type name{...};
syntax (known as uniform initialization) was added in C++11, and was supposed to become the new initialization syntax, superseding other alternatives. It has some advantages over the alternatives, so some people actively promote it.
But there's a problem with it: it behaves weirdly with some classes that weren't written with it in mind (mostly standard containers, that were designed before it was a thing).
What happens is, vector<T> name{...}
first attempts to use constructors with a std::initializer_list
parameter (which succeeds for vector<int> name{42};
, initializing the vector with a single number 42
), and if it fails, it attempts to use other contructors (vector<Day> int{42};
ends up using the constructor with a single size_t size
parameter, filling the vector with 42 default-constructed objects).
If you think that this behavior is peculiar, you're not alone.
The rule of thumb is:
Avoid the type name{...}
syntax when dealing with containers (or completely).
If you want to initialize a container with a list, use vector<T> name = {...};
.
(vector<Day> name = {42};
will not compile.)
If you want to use some other contructor of a container, use vector<T> name(...);
.
(vector<int> name(42);
will fill the vector with 42 zeroes.)
Note that vector<T> name(...);
will not work at class scope. You can use vector<T> name = vector<T>(...);
instead.
Upvotes: 5
Reputation: 310930
When the initializer is an initializer list when at first the constructor that accepts std::initializer_list is considered.
So for this declaration
std::vector<int> v{32};
there is used the constrictor of the class std::vector that has the first parameter of the type std::initializer_list<T
>.
vector(initializer_list<T>, const Allocator& = Allocator());
In this case the std::initializer_list<int>
contains only one element equal to 32.
In this declaration
std::vector<Day> day{32};
the initializer list { 32 } can not be converted to std::initializer_list<Day>
. So the constructor that accepts an initializer list is not suitable.
In this case the compiler considers another constructors and finds the constructor that accepts an integer as the number of elements in the vector.
explicit vector(size_type n, const Allocator& = Allocator());
Pay attention to that this constructor is explicit. So for example you can not write
std::vector<Day> day = {32};
From the C++ 17 Standard (13.3.1.7 Initialization by list-initialization)
1 When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:
(1.1) — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
(1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
Upvotes: 1
Reputation: 139
std::vector
has fill and initializer list constructors that suits us.
Day
can't be value-initialized to 32
, so fill constructor is called.
This can be easily verified by adding appropriate Day's constructor:
struct Day {
Day(int) {}
vector<double> hour{ vector<double>(24,not_a_reading) };
};
struct Month { // a month of temperature readings
int month{ not_a_month }; // [0:11] January is 0
vector<Day> day{ 32 }; // [1:31] one vector of readings per day
};
struct Year { // a year of temperature readings, organized by month
int year; // positive == A.D.
vector<Month> month{ 12 }; // [0:11] January is 0
};
// vector<Day> day{32};
// day.size() == 1
Upvotes: 0