auto and brace initialization

C++ · Essentials · 10 min

Modern C++ code uses auto and {} everywhere; knowing their exact rules avoids silent copies and surprising vector sizes.

auto asks the compiler to deduce a variable's type from its initializer: auto n = 42; is an int, auto d = 2.5; a double. It shines with long type names (iterators, lambdas) and keeps code correct when types change.

The rule to remember: plain auto makes a copy and drops top-level const and references. If ref is a const std::vector<int>&, then auto v = ref; copies the whole vector. Write const auto& v = ref; to bind without copying, or auto& when you want to modify the original.

C++ has several initialization syntaxes. Brace initialization works for everything and is the safest:

  • int x{5}; or int x = {5};
  • int zero{}; value-initializes to 0 (also nullptr, false, empty objects).
  • Braces reject narrowing: int n{3.7}; is a compile error, while int n = 3.7; silently truncates to 3.
  • Widget w{}; always creates an object, while Widget w(); declares a function (the "most vexing parse").

The one big gotcha: if a class has a constructor taking std::initializer_list, braces prefer it. std::vector<int> a(3, 7); is three sevens, but std::vector<int> b{3, 7}; is the two elements 3 and 7. Use parentheses when you mean "count and value".

Another subtle one: auto s = "hi"; deduces const char*, not std::string. Write std::string s = "hi"; or use the s literal suffix.

Use auto where the type is obvious or noisy, and spell the type where it documents intent.

Example

#include <iostream>
#include <string>
#include <vector>

int main() {
    auto n = 42;              // int
    auto d = 2.5;             // double
    auto s = std::string{"hi"};
    int zero{};               // value-initialised: 0
    // int bad{3.7};          // error: narrowing conversion
    int truncated = 3.7;      // compiles, silently 3 (warns with -Wconversion)

    std::vector<int> a(3, 7); // three elements: 7 7 7
    std::vector<int> b{3, 7}; // two elements: 3 7 (initializer_list wins)

    const std::vector<int>& ref = a;
    auto copy = ref;          // std::vector<int>: const and & dropped, real copy
    const auto& alias = ref;  // no copy

    std::cout << n + d << ' ' << s << ' ' << zero << ' ' << truncated << '\n';  // 44.5 hi 0 3
    std::cout << a.size() << ' ' << b.size() << ' '
              << copy.size() << ' ' << alias.size() << '\n';                   // 3 2 3 3
}

Remember

  • auto copies and drops const/&; use const auto& to avoid copies
  • Braces forbid narrowing and {} zero-initialises
  • Braces prefer initializer_list constructors: vector{3, 7} has two elements

Watch out for

  • Writing std::vector<int> v{n}; expecting n elements — it creates one element with value n.

Check yourself

  1. What is the type of `x` in `const std::string& r = name; auto x = r;`?
  2. How many elements does `std::vector<int> v{5, 0};` contain?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: en.cppreference.com/w/cpp/language/auto