Horashio
Horashio

Reputation: 13

About initializing a vector in C++ with { }

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

Answers (3)

HolyBlackCat
HolyBlackCat

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

Vlad from Moscow
Vlad from Moscow

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

xorover
xorover

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

Related Questions