Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 2 – Types, values and expressions

C++ auto, decltype and type aliases

What auto deduces from references and const, how decltype names the type of an expression, and how using makes aliases: every rule proved by the compiler.

  • Beginner
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free: 20 minutes

What you will learn

  • Use auto where the type is obvious or cannot be spelled
  • Predict what auto deduces from references and const
  • Write readable aliases with using

Before you start

What you’ll learn · Before you start

What you will learn

  • Use auto where the type is obvious or cannot be spelled
  • Predict what auto deduces from references and const
  • Write readable aliases with using

Before you start

On this page

auto in a declaration tells the compiler to take the variable’s type from its initializer: auto n = 7; makes n an int. It removes long type names, it is the only way to declare a variable whose type cannot be written, such as a lambda, and it never leaves a variable uninitialised. But it deduces the type of the value, so it drops references and the const of the initializer itself: auto x = some_reference; makes a copy. Write auto& or const auto& when you want a reference. decltype(expr) gives the type of an expression exactly as declared, references and const included. using Name = Type; creates an alias, a second name for an existing type, and also works for templates. This lesson proves every deduction with static_assert, so the compiler itself confirms each claim, on Apple clang 21 and GCC 16.

auto takes the type of the value

Each line below declares a variable with auto, and a static_assert beneath states its type; if any statement were wrong, the file would not compile. std::is_same_v<A, B> is true exactly when A and B are the same type.

deduction.cpp C++
// What auto deduces, checked by the compiler:
// each static_assert stops the build if the type is not the one it names.
#include <print>
#include <string>
#include <type_traits>
#include <vector>

int main() {
    int count = 7;
    const int limit = 10;
    int& ref = count;
    const int& cref = limit;
    const int* p = &limit;
    int scores[3] = {90, 75, 60};
    std::vector<std::string> names{"Asha", "Ravi"};

    auto a = count;        // int
    auto b = limit;        // int: the const of the variable itself is dropped
    auto c = ref;          // int: a copy, the reference is dropped
    auto d = cref;         // int: both dropped
    auto e = p;            // const int*: a pointer to const keeps that const
    auto f = scores;       // int*: an array decays to a pointer to its first element
    auto g = "chai";       // const char*: a string literal is an array of const char
    auto h = names[0];     // std::string: a copy of the element
    auto i{5};             // int (since C++17)
    auto j = {5};          // std::initializer_list<int>, not int

    static_assert(std::is_same_v<decltype(a), int>);
    static_assert(std::is_same_v<decltype(b), int>);
    static_assert(std::is_same_v<decltype(c), int>);
    static_assert(std::is_same_v<decltype(d), int>);
    static_assert(std::is_same_v<decltype(e), const int*>);
    static_assert(std::is_same_v<decltype(f), int*>);
    static_assert(std::is_same_v<decltype(g), const char*>);
    static_assert(std::is_same_v<decltype(h), std::string>);
    static_assert(std::is_same_v<decltype(i), int>);
    static_assert(std::is_same_v<decltype(j), std::initializer_list<int>>);

    c = 100;  // changes the copy only, not count
    std::println("a={} b={} c={} d={} *e={} f[0]={}", a, b, c, d, *e, f[0]);
    std::println("g={} h={} i={} j.size()={} count={}", g, h, i, j.size(), count);
}
Ten deductions, each checked by static_assert Python · deduction_session.py
from session import run

run("""
clang++ --version | head -n 1
clang++ -std=c++23 -Wall -Wextra deduction.cpp -o deduction && ./deduction
g++-16 --version | head -n 1
g++-16 -std=c++23 -Wall -Wextra deduction.cpp -o deduction && ./deduction
""")

Output

$ clang++ --version | head -n 1
Apple clang version 21.0.0 (clang-2100.3.34.2)
$ clang++ -std=c++23 -Wall -Wextra deduction.cpp -o deduction && ./deduction
a=7 b=10 c=100 d=10 *e=10 f[0]=90
g=chai h=Asha i=5 j.size()=1 count=7
$ g++-16 --version | head -n 1
g++-16 (Homebrew GCC 16.2.0) 16.2.0
$ g++-16 -std=c++23 -Wall -Wextra deduction.cpp -o deduction && ./deduction
a=7 b=10 c=100 d=10 *e=10 f[0]=90
g=chai h=Asha i=5 j.size()=1 count=7

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 deduction_session.py

The file builds on both compilers, so all ten statements hold. The rules behind them:

  • The reference is dropped. c and d come from references, but they are new int variables holding copies: after c = 100;, count is still 7.
  • Top-level const is dropped. b is a plain int although limit is const int: const describes the variable limit, and b is a different variable. A const that belongs to the type pointed to is kept, so e is const int*: it still may not change limit through the pointer.
  • Arrays become pointers. f is int*, a pointer to scores[0], and g, from a string literal, is const char*. Neither copies the array.
  • Braces need care. Since C++17 auto i{5}; is an int, but auto j = {5}; is a std::initializer_list<int>, a list with one element, which is rarely what was meant.
From const int& cref, auto drops the reference and then the top-level const, giving int; auto& keeps both, giving const int&.const int& cref = limit;(the initializer)drop the reference:const intdrop the top-level const:intauto x = cref;x is an int, a copyauto& r = cref;r is a const int&autoauto&

What auto and auto& deduce from a const reference

Text description of the diagram

The diagram starts from one initializer, const int& cref = limit;, and follows two arrows.

  • The arrow labelled auto goes down through two steps. First the reference is dropped, which leaves const int. Then the top-level const is dropped, which leaves int. So auto x = cref; makes x an int, a copy of the value.
  • The arrow labelled auto& goes to auto& r = cref;. With the & written out, nothing is dropped: r is a const int&, a reference to the same object that still cannot be changed through r.

auto&, const auto& and auto&&

When you want the original object rather than a copy, say so. The difference matters most in range-based for loops:

references.cpp C++
// auto makes a copy; auto& and const auto& refer to the original.
// The loop that doubles nothing is a classic bug.
#include <print>
#include <string>
#include <vector>

int main() {
    std::vector<int> prices{40, 25, 60};

    for (auto p : prices) p *= 2;    // p is a copy of each element: the vector does not change
    std::println("after auto:  {}", prices);

    for (auto& p : prices) p *= 2;   // p refers to each element: the vector changes
    std::println("after auto&: {}", prices);

    const std::vector<std::string> cities{"Bengaluru", "Chennai", "Kolkata"};
    std::size_t letters = 0;
    for (const auto& city : cities) letters += city.size();  // reads each string without copying it
    std::println("{} letters in {} names", letters, cities.size());

    int x = 1;
    auto&& r1 = x;   // a forwarding reference: binds to x as int&
    auto&& r2 = 42;  // ... and to a temporary as int&&
    r1 = 5;
    std::println("x = {}, r2 = {}", x, r2);
}
Copies and references in loops Python · references_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra references.cpp -o references && ./references
g++-16 -std=c++23 -Wall -Wextra references.cpp -o references && ./references
""")

Output

$ clang++ -std=c++23 -Wall -Wextra references.cpp -o references && ./references
references.cpp:10:15: warning: variable 'p' set but not used [-Wunused-but-set-variable]
   10 |     for (auto p : prices) p *= 2;    // p is a copy of each element: the vector does not change
      |               ^
1 warning generated.
after auto:  [40, 25, 60]
after auto&: [80, 50, 120]
23 letters in 3 names
x = 5, r2 = 42
$ g++-16 -std=c++23 -Wall -Wextra references.cpp -o references && ./references
references.cpp: In function 'int main()':
references.cpp:10:15: warning: variable 'p' set but not used [-Wunused-but-set-variable=]
   10 |     for (auto p : prices) p *= 2;    // p is a copy of each element: the vector does not change
      |               ^
after auto:  [40, 25, 60]
after auto&: [80, 50, 120]
23 letters in 3 names
x = 5, r2 = 42

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 references_session.py

The first loop doubled copies, so the vector did not change. Both compilers warn that p is set but never used, which is the clue here; for a copy of a class type, such as a std::string, they stay silent, so the habit of writing references in loops matters more than the warning. auto& refers to each element, so the second loop changed them. const auto& reads each string without copying it, which is the usual way to loop over strings or other large objects that you only read.

auto&& is a forwarding reference: it binds to anything, as int& to a named variable (r1 changed x) and as int&& to a temporary such as 42. You will meet it in generic code; in everyday loops auto& and const auto& say more clearly what you mean.

decltype: the declared type, exactly

decltype(e) gives the type of an expression without evaluating it, and it keeps what auto drops:

decltype_rules.cpp C++
// decltype(name) is the declared type of a variable;
// decltype((name)), with extra parentheses, is a reference.
#include <print>
#include <type_traits>
#include <vector>

int main() {
    int x = 0;
    const int c = 1;
    std::vector<int> v{1, 2, 3};

    static_assert(std::is_same_v<decltype(x), int>);
    // (x) is an expression that names an object
    static_assert(std::is_same_v<decltype((x)), int&>);
    // decltype keeps the const that auto drops
    static_assert(std::is_same_v<decltype(c), const int>);
    static_assert(std::is_same_v<decltype(v[0]), int&>);      // what operator[] returns
    static_assert(std::is_same_v<decltype(x + 1), int>);      // a computed value, not a reference

    auto copy = v[0];             // int
    decltype(auto) same = v[0];   // int&: the exact type of the initializer
    same = 10;
    copy = 20;
    std::println("v = {}, copy = {}", v, copy);
}
decltype rules, checked by static_assert Python · decltype_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra decltype_rules.cpp -o decltype_rules && ./decltype_rules
g++-16 -std=c++23 -Wall -Wextra decltype_rules.cpp -o decltype_rules && ./decltype_rules
""")

Output

$ clang++ -std=c++23 -Wall -Wextra decltype_rules.cpp -o decltype_rules && ./decltype_rules
v = [10, 2, 3], copy = 20
$ g++-16 -std=c++23 -Wall -Wextra decltype_rules.cpp -o decltype_rules && ./decltype_rules
v = [10, 2, 3], copy = 20

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 decltype_session.py

decltype(c) is const int, the declared type of c. decltype(v[0]) is int&, because operator[] returns a reference. The odd rule is the one with extra parentheses: decltype(x) names the variable and gives its declared type, int, while decltype((x)) is an expression that refers to an object, an lvalue, and so gives int&.

decltype(auto) deduces like decltype instead of like auto: same became an int&, so same = 10; changed v[0], while copy = 20; changed only the copy. It is mostly used for function return types that must keep a reference.

Where auto hides a surprise

auto takes whatever type the initializer has, even when that is not the type you had in mind.

std::vector<bool> stores its values as single bits, so flags[i] cannot return a bool&; it returns a small proxy object, std::vector<bool>::reference, that points into the vector. With auto, the variable is that proxy:

vector_bool.cpp C++
// std::vector<bool> packs its values into bits, so v[i] returns a small proxy object, not a bool&.
// auto then deduces the proxy type, which still points into the vector.
#include <print>
#include <type_traits>
#include <vector>

std::vector<bool> make_flags() { return std::vector<bool>(1000, false); }

int main() {
    std::vector<bool> flags(1000, false);

    auto first = flags[0];   // not a bool: a std::vector<bool>::reference
    static_assert(!std::is_same_v<decltype(first), bool>);
    first = true;            // writes into the vector
    std::println("flags[0] is now {}", static_cast<bool>(flags[0]));

    bool copy = flags[1];    // naming the type converts the proxy to a plain bool
    copy = true;
    std::println("flags[1] is still {}, copy is {}", static_cast<bool>(flags[1]), copy);

    auto dangling = make_flags()[0];  // a proxy into a temporary, destroyed at the end of this line
    std::println("reading it: {}", static_cast<bool>(dangling));  // reads freed memory
}
std::vector<bool> and auto, with AddressSanitizer Python · vector_bool_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined vector_bool.cpp -o vector_bool && ./vector_bool
g++-16 -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined vector_bool.cpp -o vector_bool && ./vector_bool
""")

Output

$ clang++ -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined vector_bool.cpp -o vector_bool && ./vector_bool
flags[0] is now true
flags[1] is still false, copy is true
=================================================================
==…==ERROR: AddressSanitizer: heap-use-after-free on address 0x…
READ of size 8 at 0x… thread T0
    #1 in main vector_bool.cpp:22
0x… is located 0 bytes inside of 128-byte region [0x…,0x…)
freed by thread T0 here:
    #7 in main vector_bool.cpp:21
previously allocated by thread T0 here:
    #9 in make_flags() vector_bool.cpp:7
    #10 in main vector_bool.cpp:21
SUMMARY: AddressSanitizer: heap-use-after-free
[56 more lines of the report left out]
[exit status 134]
$ g++-16 -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined vector_bool.cpp -o vector_bool && ./vector_bool
flags[0] is now true
flags[1] is still false, copy is true
=================================================================
==…==ERROR: AddressSanitizer: heap-use-after-free on address 0x…
READ of size 8 at 0x… thread T0
    #1 in main vector_bool.cpp:22
0x… is located 0 bytes inside of 128-byte region [0x…,0x…)
freed by thread T0 here:
    #5 in main vector_bool.cpp:21
previously allocated by thread T0 here:
    #5 in make_flags() vector_bool.cpp:7
    #6 in main vector_bool.cpp:21
SUMMARY: AddressSanitizer: heap-use-after-free
[50 more lines of the report left out]
[exit status 134]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vector_bool_session.py

first = true; wrote into the vector, while the bool copy did not. (AddressSanitizer reports run to dozens of lines, so the sessions keep the lines that name the error and this lesson’s own files, and write the memory addresses, which change on every run, as 0x….) The last two lines are the dangerous case: make_flags()[0] is a proxy into a temporary vector that is destroyed at the end of the line, so reading it reads freed memory. AddressSanitizer stops the program and names both lines, line 21 where the memory was allocated and freed and line 22 where it was read; without the sanitizer the program might print a plausible value. Write the type (bool b = flags[0];) when you want the value.

Number types are the other surprise. A literal or an expression has a type of its own, and auto keeps it:

integer_auto.cpp C++
// auto takes the type of a literal or an expression exactly as it is,
// which is not always the type you wanted.
#include <cstddef>
#include <print>
#include <type_traits>
#include <vector>

int main() {
    auto small = 3'000;          // int
    auto big = 3'000'000'000;    // too big for int: long here, long long on Windows
    auto size = std::vector<int>(5).size();  // std::size_t, an unsigned type
    static_assert(std::is_same_v<decltype(small), int>);
    static_assert(std::is_same_v<decltype(big), long>);
    static_assert(std::is_same_v<decltype(size), std::size_t>);

    std::vector<int> v{3, 1, 4};
    for (auto i = 0; i < v.size(); ++i) {  // int i against an unsigned size: a mixed comparison
        std::print("{} ", v[i]);
    }
    std::println("| sizes in bytes: {} {} {}", sizeof small, sizeof big, sizeof size);
}
auto with literals and sizes Python · integer_auto_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra integer_auto.cpp -o integer_auto && ./integer_auto
g++-16 -std=c++23 -Wall -Wextra integer_auto.cpp -o integer_auto && ./integer_auto
""")

Output

$ clang++ -std=c++23 -Wall -Wextra integer_auto.cpp -o integer_auto && ./integer_auto
integer_auto.cpp:17:24: warning: comparison of integers of different signs: 'int' and 'size_type' (aka 'unsigned long') [-Wsign-compare]
   17 |     for (auto i = 0; i < v.size(); ++i) {  // int i against an unsigned size: a mixed comparison
      |                      ~ ^ ~~~~~~~~
1 warning generated.
3 1 4 | sizes in bytes: 4 8 8
$ g++-16 -std=c++23 -Wall -Wextra integer_auto.cpp -o integer_auto && ./integer_auto
integer_auto.cpp: In function 'int main()':
integer_auto.cpp:17:24: warning: comparison of integer expressions of different signedness: 'int' and 'std::vector<int>::size_type' {aka 'long unsigned int'} [-Wsign-compare]
   17 |     for (auto i = 0; i < v.size(); ++i) {  // int i against an unsigned size: a mixed comparison
      |                      ~~^~~~~~~~~~
3 1 4 | sizes in bytes: 4 8 8

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 integer_auto_session.py

3'000'000'000 does not fit in a 32-bit int, so the literal is a long, 8 bytes on 64-bit macOS and Linux. On Windows long has 4 bytes, so the same literal is a long long there. size() returns std::size_t, an unsigned type, while auto i = 0 makes an int, and the comparison of the two in the loop condition gets a -Wsign-compare warning from both compilers. In such a comparison the int is converted to the unsigned type, so a negative value would compare as a huge number. Declare the loop variable as std::size_t, or loop over the elements with for (int x : v).

Type aliases with using

using Name = Type; gives an existing type a second name. It reads left to right, like an assignment, and unlike the older typedef it can also be a template:

aliases.cpp C++
// using gives an existing type a second name. It does not create a new type,
// so mixing up two aliases of the same type still compiles.
#include <cstdint>
#include <map>
#include <print>
#include <string>
#include <type_traits>
#include <vector>

using Score = std::int32_t;
// much shorter to write than the type it names
using Marks = std::map<std::string, std::vector<Score>>;

template <typename T>
// an alias template: typedef cannot do this
using Grid = std::vector<std::vector<T>>;

typedef std::int32_t OldScore;                            // the older way to write the same alias

using Metres = double;
using Seconds = double;

int main() {
    static_assert(std::is_same_v<Score, std::int32_t>);
    static_assert(std::is_same_v<Score, OldScore>);

    Marks marks{{"Asha", {78, 91}}, {"Ravi", {64}}};
    Grid<char> board(2, std::vector<char>(3, '.'));
    board[1][2] = 'X';

    const Metres distance = 1500.0;
    const Seconds time = 300.0;
    const double nonsense = distance + time;  // compiles: both are just double

    std::println("{} students, board {}x{}, row 1 = {}, distance + time = {}", marks.size(), board.size(),
                 board[0].size(), std::string(board[1].begin(), board[1].end()), nonsense);
}
Aliases and alias templates Python · aliases_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra aliases.cpp -o aliases && ./aliases
g++-16 -std=c++23 -Wall -Wextra aliases.cpp -o aliases && ./aliases
""")

Output

$ clang++ -std=c++23 -Wall -Wextra aliases.cpp -o aliases && ./aliases
2 students, board 2x3, row 1 = ..X, distance + time = 1800
$ g++-16 -std=c++23 -Wall -Wextra aliases.cpp -o aliases && ./aliases
2 students, board 2x3, row 1 = ..X, distance + time = 1800

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 aliases_session.py

Marks saves writing std::map<std::string, std::vector<std::int32_t>> everywhere, and Grid<char> is a std::vector<std::vector<char>>. The C++ Core Guidelines recommend using over typedef (rule T.43) for these reasons.

An alias is not a new type. Score and std::int32_t are the same type, which the first static_assert proves, and so are Metres and Seconds: adding a distance to a time compiled and printed 1800. When two kinds of value must never be mixed, make them different types: small structs (a later module) or an enum class.

When to use auto

  • Use it when the type is already visible in the initializer (auto p = std::make_unique<Order>();, auto n = static_cast<int>(x);), when the type is long and adds nothing (iterators, map entries), and when it cannot be written at all, as for lambdas. The Core Guidelines give the same advice in rule ES.11.
  • Add & or const auto& whenever you mean the original object, especially in loops.
  • Write the type when the exact type is the point: integer widths, bool from std::vector<bool>, a value that must not narrow.

Exercise: predict ten types

For each declaration in the file, replace void with the type that auto or decltype deduces on that line. The static_assert lines check every answer while the file compiles, so the exercise is solved when it builds. Write your answers first; a failing check then shows the real type, so you can learn from each one.

predict_types.cpp C++
// Exercise: replace each void with the type that auto or decltype deduces on the same line.
// The static_asserts at the end check every answer: it is solved when the file builds.
#include <string>
#include <type_traits>
#include <vector>

int main() {
    const int limit = 10;
    int count = 3;
    int& ref = count;
    const std::string name = "Meera";
    std::vector<int> v{1, 2, 3};
    std::vector<bool> bits(8);

    auto t1 = limit;               using answer1 = void;
    auto& t2 = limit;              using answer2 = void;
    auto t3 = ref;                 using answer3 = void;
    decltype(ref) t4 = count;      using answer4 = void;
    decltype((count)) t5 = count;  using answer5 = void;
    auto t6 = name.size();         using answer6 = void;
    auto t7 = v[0] * 2.0;          using answer7 = void;
    const auto& t8 = name;         using answer8 = void;
    auto t9 = 'a' + 1;             using answer9 = void;
    auto t10 = bits[0];            using answer10 = void;

    static_assert(std::is_same_v<decltype(t1), answer1>, "t1");
    static_assert(std::is_same_v<decltype(t2), answer2>, "t2");
    static_assert(std::is_same_v<decltype(t3), answer3>, "t3");
    static_assert(std::is_same_v<decltype(t4), answer4>, "t4");
    static_assert(std::is_same_v<decltype(t5), answer5>, "t5");
    static_assert(std::is_same_v<decltype(t6), answer6>, "t6");
    static_assert(std::is_same_v<decltype(t7), answer7>, "t7");
    static_assert(std::is_same_v<decltype(t8), answer8>, "t8");
    static_assert(std::is_same_v<decltype(t9), answer9>, "t9");
    static_assert(std::is_same_v<decltype(t10), answer10>, "t10");
    (void)t1; (void)t2; (void)t3; (void)t4; (void)t5; (void)t6; (void)t7; (void)t8; (void)t9; (void)t10;
}

As given, all ten checks fail. The session counts them without showing the compiler’s messages, which contain the answers:

Failing checks in the exercise as given Python · predict_types_session.py
from session import run

run("""
# How many of the ten checks fail? grep -c counts the lines of the compiler's output that report one.
clang++ -std=c++23 -fsyntax-only predict_types.cpp 2>&1 | grep -c 'static assertion failed'
g++-16 -std=c++23 -fsyntax-only predict_types.cpp 2>&1 | grep -c 'static assertion failed'
""")

Output

# How many of the ten checks fail? grep -c counts the lines of the compiler's output that report one.
$ clang++ -std=c++23 -fsyntax-only predict_types.cpp 2>&1 | grep -c 'static assertion failed'
10
$ g++-16 -std=c++23 -fsyntax-only predict_types.cpp 2>&1 | grep -c 'static assertion failed'
10

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 predict_types_session.py

First hint: start from the three rules of auto (references dropped, top-level const dropped, arrays and functions become pointers), remember that decltype keeps references, and that 'a' + 1 is computed in the type that char is promoted to.

Self-check

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What are the types of a and b?

    Read the code, then choose one answer.

    const std::string name = "Asha";
    auto a = name;
    auto& b = name;
    Show the answer to question 1

    Answer: a is std::string, b is const std::string&

    auto drops the top-level const, so a is a modifiable copy. With auto& nothing is dropped, and the reference must keep the const of the object it refers to.

  2. Question 2 of 5 What does v hold after this loop?

    Read the code, then choose one answer.

    std::vector<int> v{1, 2, 3};
    for (auto x : v) x = 0;
    Show the answer to question 2

    Answer: [1, 2, 3]

    Each x is a copy of an element, so setting it to 0 changes only the copy. Both compilers warn that x is set but never used. for (auto& x : v) would set the elements to 0.

  3. Question 3 of 5 int x = 0; and std::vector<int> v{1, 2, 3}; are declared. Which of these name the type int&?

    Choose every answer that is right.

    Show the answer to question 3

    Answer:

    • decltype(v[0])
    • decltype((x))

    decltype(x) is the declared type, int, and x + 1 is a computed value of type int. Extra parentheses make (x) an expression that refers to an object, so decltype((x)) is int&, and v[0] calls operator[], which returns int&.

  4. Question 4 of 5 What type does auto j = {5}; give j?

    Choose one answer.

    Show the answer to question 4

    Answer: std::initializer_list<int>

    With = and braces, auto deduces a std::initializer_list. Without the =, auto j{5}; deduces int (since C++17).

  5. Question 5 of 5 With using Metres = double; and using Seconds = double;, what happens to Metres m = Seconds{3.0};?

    Choose one answer.

    Show the answer to question 5

    Answer: It compiles: both aliases name the same type, double

    An alias is another name for an existing type, not a new type, so the compiler sees double m = double{3.0};. To make such mistakes errors, give each unit a type of its own.

Interview questions

What does auto deduce for auto x = r; when r is a const int&? It deduces int. auto deduces the type of the value, so it drops the reference and then the top-level const, and x is a separate, modifiable copy. To keep the reference write auto& x = r;, which deduces const int& because the const now belongs to the object referred to; const auto& x = r; says the same thing more explicitly. decltype(r) x = r; would also give const int&.

Key takeaways

  • auto deduces the type of the initializer’s value: references and top-level const are dropped, arrays become pointers.
  • auto& and const auto& keep the original object; use them in loops over elements you change or only read.
  • auto&& binds to anything and is mostly for generic code.
  • decltype(x) is the declared type, decltype((x)) an lvalue reference, and decltype(auto) deduces with decltype rules.
  • auto keeps proxy types (std::vector<bool>::reference) and literal types (long or long long): write the type when it matters.
  • using Name = Type; makes an alias, also for templates; an alias is not a new type.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.