Skip to content
VLSI Mentor

C++ Part I · Module 3 · Types, Variables, and Initialization

Initialization: The Forms and Which to Prefer

Every example in this module has quietly done the same thing: given an object a value on the line that declared it. That looks like one operation with one obvious spelling, and C++ actually has several, which do not all mean the same thing. Most of the time they agree and the choice is cosmetic. The interesting case is when they disagree, and they disagree about something that matters: whether to accept a value that does not fit in the type you are storing it in. One form refuses and gives you a compile error. The others accept it, convert the value, and — in the case this chapter leads with — say nothing at all, even at the strongest warning level this curriculum uses. That is a silent loss of data in a program that builds cleanly, which is the worst shape a defect can have. This chapter is short on syntax and long on consequence, and the habit it establishes shows up in every line of C++ you write afterwards.

Foundation16 min readC++Part IFundamentalsInitialization

Part I · Module 3 · Chapter 3.7 · Initialization: The Forms and Which to Prefer

1. Why This Is Not a Syntax Lesson

Giving an object its first value is called initialization, and C++ has more than one way to spell it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int a = 42;     // copy initialization
int b(42);      // direct initialization
int c{42};      // direct-list initialization — "brace initialization"
int d{};        // value initialization

All four compile. The first three all end up holding 42, and the fourth holds 0:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a=42 b=42 c=42 d=0

If that were the whole story, this would be a style question and the chapter would be two paragraphs long.

It is not the whole story, because the forms differ on one question: what happens when the value you supply does not fit the type you are storing it in? That is where they part company, and the difference is the subject of §4.

2. What Initialization Is For

Before the forms, the reason.

An object's first value has to come from somewhere. You can set it on the declaring line, or you can declare the object and assign to it later:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int cycle_count = 19;      // initialization: a value from the start

    int error_count;           // no initial value
    error_count = 0;           // assignment: a value later

These are different operations, and the difference is not only timing. Initialization gives an object its first value; assignment replaces an existing value. For an int the distinction looks academic. From Module 12 onward it stops being academic, because types can do genuinely different work in the two cases.

The practical reason to prefer initialization is simpler and applies immediately: between the declaration and the assignment, the object has no useful value, and reading it there is a serious error. How serious is Chapter 3.8's subject — the next chapter. For now, one rule covers it:

Give an object a value on the line that declares it, whenever you can.

3. The Four Forms

Each with its name, because the names appear in compiler diagnostics and you will want to recognise them.

Copy initialization — with =:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int cycle_count = 19;

The oldest form, and the one that reads most naturally.

Direct initialization — with parentheses:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int cycle_count(19);

For an int this is equivalent to the first. It becomes meaningfully different from Module 12, where it connects to constructors.

Direct-list initialization — with braces. Commonly called brace initialization or uniform initialization:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int cycle_count{19};

Added in C++11. This is the one with the extra property.

Value initialization — with empty braces:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int cycle_count{};         // 0

This gives the object a well-defined default. For the built-in numeric types that is zero; for bool it is false. It is the shortest way to say "start at the natural empty value" and it is never ambiguous about your intent.

4. Where the Forms Disagree

Now the part that matters. Consider a measured delay that you want as a whole number:

Azvya Education Pvt. Ltd.VLSI Mentor
narrow_var.cpp — complete program. Compiles with zero warnings.
#include <iostream>

int main()
{
    double measured_delay = 3.7;
    int    rounded = measured_delay;      // accepted, value silently truncated

    std::cout << "rounded = " << rounded << '\n';
    return 0;
}

Build it at the strongest warning level this curriculum uses:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ c++ -std=c++17 -Wall -Wextra -Wpedantic narrow_var.cpp -o narrow_var
$                                            ← nothing. Not one word.
$ ./narrow_var
rounded = 3

Zero diagnostics, and 0.7 is gone.

This is a narrowing conversion: converting a value to a type that cannot represent all the values of the source type. A double can hold 3.7; an int cannot. Something has to give, and what gave was the fractional part — silently.

Now change one character. Braces instead of =:

Azvya Education Pvt. Ltd.VLSI Mentor
narrow_var_b.cpp — INCORRECT. Does not compile, and that is the point.
    double measured_delay = 3.7;
    int    rounded{measured_delay};
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
narrow_var_b.cpp:5:20: error: type 'double' cannot be narrowed to 'int'
                       in initializer list [-Wc++11-narrowing]

A compile error. The same intent, the same data loss, caught before the program exists.

That is the argument for brace initialization, and it is not a matter of taste:

Brace initialization rejects narrowing conversions. The other forms accept them.

5. Narrowing Is Not Only About Fractions

The same protection applies between integer types:

Azvya Education Pvt. Ltd.VLSI Mentor
narrow_big.cpp — INCORRECT. Does not compile.
int main()
{
    int  big = 300;
    char small{big};       // 300 does not fit in a char
    return small;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
narrow_big.cpp:4:16: error: non-constant-expression cannot be narrowed
                     from type 'int' to 'char' in initializer list
                     [-Wc++11-narrowing]

A char on this platform holds −128 to 127 (Chapter 3.6 §6). 300 does not fit. With = this would compile and store some other value.

Read the diagnostic's wording: non-constant-expression. That is the rule being precise. If the source is a constant the compiler can evaluate, and the value does fit, braces allow it — because there is nothing to lose:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    char fits{65};         // fine: 65 is a constant and fits in a char

So braces are not refusing all conversions. They are refusing conversions that could lose information, which is a narrower and more useful rule.

6. A Realistic Example

Where this earns its place in engineering code:

Azvya Education Pvt. Ltd.VLSI Mentor
divider.cpp — the kind of mistake braces catch
#include <cstdint>
#include <iostream>

int main()
{
    const double measured_frequency = 133.7;      // MHz, from a measurement
    const double reference          = 25.0;       // MHz

    // The divider must be a whole number — it configures a hardware register.
    std::uint32_t divider = measured_frequency / reference;   // silent: 5

    std::cout << "divider = " << divider << '\n';
    return 0;
}

Expected output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
divider = 5

The real ratio is 5.348. The register gets 5, the clock is wrong, and nothing in the build said a word.

With braces the line does not compile, and the engineer is forced to decide what they actually meant — round down, round to nearest, or report that the frequency is not an exact multiple. That is the value: not that braces produce the right answer, but that they refuse to let an unexamined one through.

The fix is to make the intent explicit rather than to abandon braces. The tool for saying "yes, convert this deliberately" is static_cast, which you met in Chapter 1.4 §7, and Chapter 4.10 teaches properly.

7. Value Initialization and the Empty Braces

One form deserves its own note:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int           error_count{};      // 0
    double        total_delay{};      // 0.0
    bool          parity_error{};     // false
    std::uint32_t status{};           // 0

Empty braces give the type's natural zero value. This is useful because:

  • It is unambiguous. int error_count{}; says "start at zero" more clearly than int error_count = 0; says anything, and far more clearly than leaving it out.
  • It works the same way for every type, including ones you will define later. Writing {} does not require knowing what the zero value of a type is.
  • It is impossible to get wrong by forgetting, in a way = 0 is not when you later change the type.

Compare with the form that looks similar and is not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int error_count;        // NOT zero. No value at all.
    int error_count{};      // zero.

Those two lines differ by two characters and by a great deal of meaning. The first is Chapter 3.8's entire subject.

8. So Should You Always Use Braces?

A reasoned default rather than a rule, because the honest answer has a caveat.

The default: prefer braces for initializing variables. You get narrowing protection for free, the empty form gives you a reliable zero, and it looks the same everywhere.

The caveat: brace initialization interacts with two features you have not met, and in those contexts it is not simply the safest choice.

  • Constructors. From Module 12, a type can define what initializing it means. Braces and parentheses can then select different constructors.
  • std::initializer_list. Some library types — std::vector most famously — treat a braced list as a list of elements rather than arguments. std::vector<int> v{3, 4}; gives you two elements, not three fours. Module 19 covers it.

Neither affects the built-in types in this module. Both are real, and pretending otherwise would mean teaching you a rule you have to unlearn.

For the fundamental types in this module: use braces. When you reach Modules 12 and 19, the picture gets a wrinkle, and those chapters will tell you where.

That is also why this chapter does not say "always use braces for everything". A curriculum that teaches a rule without its boundary is teaching something you will have to be re-taught.

9. Mistakes Worth Making Once

Declaring without initializing out of habit. int error_count; leaves the object with no useful value. Chapter 3.8 is about exactly how bad that is.

Assuming = will warn you about a narrowing conversion. With a literal it does; with a variable it is silent. The silent case is the one in real code.

Reading a clean build as evidence of correctness. divider = 5 compiled without a word and configured the wrong clock divisor. Chapter 2.6 §8 made the same point about linking.

Confusing int x; with int x{};. Two characters apart; one has no value and the other has zero.

Thinking braces reject all conversions. They reject narrowing ones. char fits{65}; is fine because the constant fits.

Carrying "always use braces" into Modules 12 and 19 without the caveat. std::vector<int> v{3, 4}; does not mean what the parenthesised form means.

Using = because it reads more naturally and never revisiting it. That is a reasonable instinct and it costs you the one protection the forms differ on.

10. Interview Relevance

This chapter carries a Critical weighting, and the question behind it is a good discriminator.

"What is the difference between int x = 5; and int x{5};?" is not asking you to recite four form names. It is checking whether you know that one of them rejects narrowing conversions and the other does not, and ideally whether you can say why that matters more than it sounds.

A strong answer names the protection, gives an example where data is silently lost, and then volunteers the caveat — that braces interact with constructors and initializer_list in ways that make "always braces" too simple. That last part is what distinguishes someone who has used C++11 initialization from someone who has read a style guide about it.

11. What You Should Be Able to Say Now

  1. Initialization gives an object its first value; assignment replaces an existing one. They are different operations, and the difference grows from Module 12 onward.
  2. Initialize on the declaring line whenever you can.
  3. Four forms: copy (= 42), direct ((42)), direct-list ({42}), value ({}).
  4. Value initialization with empty braces gives the type's natural zero — 0, 0.0, false.
  5. Brace initialization rejects narrowing conversions; the other forms accept them.
  6. Measured: int rounded = measured_delay; from a double compiled with zero warnings and stored 3. The brace form was a compile error.
  7. With a literal source, = does warn. With a variable source it is completely silent — and that is the case that occurs in real code.
  8. Narrowing is not only about fractions: char small{big}; from an int is also rejected.
  9. Braces reject conversions that could lose information. A constant that fits, like char fits{65};, is accepted.
  10. int x; and int x{}; differ by two characters and by whether the object has a value at all.
  11. Use braces as the default for fundamental types. Modules 12 and 19 add a wrinkle involving constructors and std::initializer_list.
  12. The value of braces is not that they give the right answer — it is that they refuse to let an unexamined conversion through silently.

12. Exercises

Exercise 1 — predict four results. For each, say whether it compiles and what it holds: (a) int a = 42; (b) int b{42}; (c) int c{}; (d) int d;.

Exercise 2 — which one fails? Say which of these is a compile error and why, given that rate is a double holding 2.5: (a) int n = rate; (b) int n{rate}; (c) double m{rate};.

Exercise 3 — explain the asymmetry. int count = 3.7; produced a warning but int rounded = measured_delay; produced none. Explain what the compiler could see in one case and not the other, and say which case matters more in practice.

Exercise 4 — fix the divider. The divider.cpp example silently stored 5 for a true ratio of 5.348. Rewrite it so the truncation cannot happen unnoticed, and state what decision the engineer is now forced to make.

Exercise 5 — the constant that fits. char fits{65}; compiles and char small{big}; does not, where big is an int variable holding 65. Explain the difference, quoting the phrase from the diagnostic that gives it away.

Exercise 6 — two characters apart. Describe the difference between std::uint32_t status; and std::uint32_t status{}; and say which chapter covers what goes wrong with the first.

Exercise 7 — argue the caveat. A colleague states the rule "always use braces". Give the case for it in one sentence, then name the two later features that complicate it and what each does differently.

13. Summary

  • Initialization is not assignment. One gives a first value; the other replaces one.
  • Initialize on the declaring line whenever you can.
  • Four forms: copy = 42, direct (42), direct-list {42}, value {}.
  • Empty braces give the natural zero for any type, including ones you define later.
  • Braces reject narrowing conversions; the other forms accept them. That is the only difference that matters for fundamental types, and it is enough.
  • Measured: int rounded = measured_delay; compiled with zero warnings and lost 0.7. The brace form was a compile error.
  • With a literal, = warns. With a variable, it is silent — which is the real-code case.
  • Narrowing covers integers too, and braces still allow a constant that genuinely fits.
  • Default to braces for the fundamental types, and expect a documented wrinkle at Modules 12 and 19.
  • The point is not getting the right answer. It is refusing to let an unexamined one through.

Initialization has an opposite, and it is the subject of the next chapter. Everything above assumed you gave the object a value; §7 noted that int error_count; and int error_count{}; differ by two characters, and left the first one hanging. What is actually in an object that was declared and never initialized is a question with a more careful answer than "garbage" — and reading one is not merely unpredictable, it is a specific category of error with specific consequences. Chapter 3.8 is that answer, and it is the chapter where this module's accuracy matters most.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.