Variable Declaration and Initialization
Uniform and general initialization use {}-lists as a single notation for giving values to variables.
Two Ways to Start a Variable
Giving a variable its first value looks simple. You name the variable, state its type, and provide a value. C++11 adds a second way to write that same step, using braces. The second way can behave differently in an important case, so it is worth understanding both forms and when they diverge.
The form this article focuses on is the {}-list form. The source describes it as the basis for uniform and general initialization, meaning a single notation for giving values to variables. The difference between the two forms lies in how the braces change what the compiler is willing to accept. Because the {}-list form is the one the source highlights, it is the notation to reach for when initializing.
- The first line uses the equals-sign form.
- The second line uses the {}-list form with the same value and the same target type.
Anatomy of a {}-List Initialization
A {}-list initialization has the same basic parts as any declaration with a starting value: a type, a variable name, and a value. The braces wrap the value. The source treats this braces form as a single notation that covers initialization in general, which is why it is called uniform. The diagram below labels each piece of the declaration.
Tracing a Value into a Variable
To see what the braces do, follow a single value from the initializer into the variable. Start with a double holding a fractional value and ask what happens when that value is placed into an int. The same value and the same target type appear in both versions below. Only the form of initialization changes.
double value = 3.7;
int count = value;The equals-sign form places the fractional value into an int variable.
double value = 3.7;
int count{value};The braces form attempts the same placement and is refused, because the conversion can lose information.
The equals-sign form accepts the conversion, and the fractional part is dropped when the value reaches the integer. The source states that this equals-sign form can accept a truncating conversion that the braces form rejects. That refusal is the prevention of narrowing: a {}-list initialization will not accept a conversion that can lose information.
What do you think happens?
If a double holding 3.7 is used to initialize an int with braces, will the compiler accept it?
Reveal answer
No, the braces form refuses the conversion
Prevention of narrowing means a {}-list initialization refuses conversions that can lose information. Placing 3.7 into an int would drop the fractional part, so the braces form rejects it. The equals-sign form would accept it.
How the Compiler Decides on a {}-List Value
The question to ask for every value placed in a {}-list is whether the target type can keep the whole value. If it can, the conversion is safe for this purpose and the initialization is accepted. If it cannot, the conversion is narrowing and the {}-list form refuses it. The flowchart below follows that decision.
What Counts as Narrowing
Not every conversion is narrowing. The useful question is whether the target type can hold the value without losing information. A conversion that keeps the information is safe for this purpose, while one that drops part of the value is narrowing. The comparison below sets the common cases side by side.
| Conversion | Can the target keep the whole value? | Result with {}-list form |
|---|---|---|
| double to int, value 3.7 | No, the fractional part would be dropped | Refused as narrowing |
| int to double, a value the double holds | Yes for the case checked | Accepted |
| int to double, another value | Depends on the value, so check it | Decide case by case |
Applying the keep-the-whole-value test to the source's cases
Mistakes Beginners Make with {}-Lists
Common mistake Assuming the braces form and the equals-sign form always behave the same
What it looks likeint count = 3.7; compiles, while int count{3.7}; is refusedWhy it failsThe equals-sign form can accept a truncating conversion that the braces form rejects.
Do this insteadExpect the braces form to refuse any conversion that can lose information, and check the value before switching forms.
Common mistake Treating every int-to-double conversion as safe or every one as narrowing
Why it failsWhether an int-to-double conversion is accepted depends on the particular value involved.
Do this insteadAsk whether the target type can keep the whole value for that specific value.
Common mistake Thinking the braces only change the look of the code
Why it failsThe braces change what the compiler is willing to accept, not only the appearance of the declaration.
Do this insteadRead the braces as a rule that refuses narrowing conversions.
Practice Deciding Narrowing
For each declaration below, decide whether the {}-list form would accept it or refuse it as narrowing: (1) int a{3.7}; (2) double b{5}; (3) int c{2.0}. Explain your decision using the question of whether the target type can keep the whole value.
Hints
- Ask whether the int can hold the whole fractional value in 3.7.
- Ask whether the double can keep the whole value 5.
- Consider the value 2.0 carefully: the test is whether the int can keep the whole value, and the second case depends on the value.
Key Takeaways
- Uniform and general initialization use {}-lists as a single notation for giving values to variables.
- The {}-list form refuses conversions that can lose information; this is the prevention of narrowing.
- The equals-sign form can accept a truncating conversion, such as a fractional value placed into an int, that the braces form rejects.
- To judge a conversion, ask whether the target type can keep the whole value; for int-to-double, check the particular value rather than memorizing a rule.