Concepts / Variable Declaration and Initialization

Variable Declaration and Initialization

Uniform and general initialization use {}-lists as a single notation for giving values to variables.

  • Programming

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.

cpp
Notes on the code
  • 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.

intthe typetotalthe variable name{opens the {}-list3.7the value being placed}closes the {}-listdeclaresinitialized bycontainsends list
Which piece of the declaration names the type, the variable, and the starting value when the braces form is used?

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.

Before
double value = 3.7;
int count = value;

The equals-sign form places the fractional value into an int variable.

After
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?

  • Yes, and the variable holds 3

  • No, the braces form refuses the conversion

Reveal answer
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.

Value in {}-listCan target type keepthe whole value?Accept initializationconversion is safeReject initializationconversion is narrowingcheckedyesno
When a value is placed in a {}-list, at which point is the initialization accepted or rejected?

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.

double to intfractional part is lostint to doubledepends on the particularvalueKeeps the whole valuesafe for this purpose
Which conversions lose information, which keep it, and which need the value checked?
ConversionCan the target keep the whole value?Result with {}-list form
double to int, value 3.7No, the fractional part would be droppedRefused as narrowing
int to double, a value the double holdsYes for the case checkedAccepted
int to double, another valueDepends on the value, so check itDecide 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 like
    int count = 3.7; compiles, while int count{3.7}; is refused
    Why it fails

    The equals-sign form can accept a truncating conversion that the braces form rejects.

    Do this instead

    Expect 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 fails

    Whether an int-to-double conversion is accepted depends on the particular value involved.

    Do this instead

    Ask 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 fails

    The braces change what the compiler is willing to accept, not only the appearance of the declaration.

    Do this instead

    Read the braces as a rule that refuses narrowing conversions.

Practice Deciding Narrowing

MEDIUM

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.

Related concepts

More concepts starting with V