What is Type Deduction for Objects?

Type deduction lets the compiler figure out a variable's type from its initializer, so you can write auto x { 5 }; instead of int x { 5 };. This eliminates redundancy when the type is already clear from the initialization value.

There's a subtle redundancy in this simple variable definition:

double price{ 19.99 };

In C++, we must provide an explicit type for all objects. Thus, we've specified that variable price is of type double.

However, the literal value 19.99 used to initialize price also has type double (implicitly determined via the literal's format).

We discuss how literal types are determined in the lesson on literals.

In cases where we want a variable and its initializer to have the same type, we're effectively providing the same type information twice.

Type Deduction for Initialized Variables

Type deduction (also called type inference) is a feature allowing the compiler to deduce an object's type from its initializer. When defining a variable, type deduction can be invoked by using the auto keyword in place of the variable's type:

int main()
{
    auto price { 19.99 }; // 19.99 is a double literal, so price deduced as double
    auto total { 10 + 20 }; // 10 + 20 evaluates to int, so total deduced as int
    auto count { total }; // total is int, so count deduced as int

    return 0;
}

In the first case, because 19.99 is a double literal, the compiler deduces variable price should be type double. In the second case, expression 10 + 20 yields an int result, so variable total will be type int. In the third case, total was previously deduced as type int, so count will also be deduced as type int.

Warning
Prior to C++17, `auto price{ 19.99 };` would deduce `price` to be type `std::initializer_list` rather than `double`. This was fixed in C++17, and many compilers (such as gcc and Clang) have back-ported this change to previous language standards.

If you're using C++14 or older and the above example doesn't compile on your compiler, use copy initialization with `auto` instead (`auto price = 19.99`).

Because function calls are valid expressions, we can use type deduction when our initializer is a non-void function call:

int calculateSum(int a, int b)
{
    return a + b;
}

int main()
{
    auto result { calculateSum(15, 25) }; // calculateSum() returns int, so result's type deduced as int

    return 0;
}

The calculateSum() function returns an int value, so the compiler deduces variable result should have type int.

Literal suffixes can be used with type deduction to specify a particular type:

int main()
{
    auto temperature { 98.6f }; // f suffix causes temperature to be deduced to float
    auto count { 50u }; // u suffix causes count to be deduced to unsigned int

    return 0;
}

Variables using type deduction may also use other specifiers/qualifiers, such as const or constexpr:

int main()
{
    int value { 42 }; // value is an int

    const auto limit { 100 }; // limit is a const int
    constexpr auto max { 255 }; // max is a constexpr int

    return 0;
}

Type Deduction Must Have Something to Deduce From

Type deduction won't work for objects lacking initializers or having empty initializers. It also won't work when the initializer has type void (or any other incomplete type). Thus, the following is invalid:

#include <iostream>

void doNothing()
{
}

int main()
{
    auto x; // compiler can't deduce x's type
    auto y { }; // compiler can't deduce y's type
    auto z { doNothing() }; // invalid: z can't have incomplete type void

    return 0;
}

Although using type deduction for fundamental data types saves only a few keystrokes (if any), in future lessons we'll see examples where types become complex and lengthy (and can be hard to determine). In those cases, using auto can save significant typing (and typos).

Type deduction rules for pointers and references are more complex (discussed in a later lesson on type deduction with pointers, references, and const).

Type Deduction Drops const From the Deduced Type

In most cases, type deduction drops the const from deduced types. Example:

int main()
{
    const int maximum{ 500 }; // maximum has type const int
    auto current { maximum }; // current has type int (const dropped)

    return 0;
}

In the above example, maximum has type const int, but when deducing a type for variable current using maximum as the initializer, type deduction deduces the type as int, not const int.

If you want a deduced type to be const, you must supply the const yourself as part of the definition:

int main()
{
    const int maximum{ 500 }; // maximum has type const int
    const auto current { maximum }; // current has type const int (const dropped but reapplied)

    return 0;
}

In this example, the type deduced from maximum will be int (the const is dropped), but because we've re-added a const qualifier during variable current's definition, variable current will have type const int.

Type Deduction for String Literals

For historical reasons, string literals in C++ have a strange type. Therefore, the following probably won't work as expected:

auto message { "Error occurred" }; // message will be type const char*, not std::string

If you want the type deduced from a string literal to be std::string or std::string_view, you'll need to use the s or sv literal suffixes (introduced in earlier lessons on std::string and std::string_view):

#include <string>
#include <string_view>

int main()
{
    using namespace std::literals; // easiest way to access s and sv suffixes

    auto greeting { "Hello"s }; // "Hello"s is std::string literal, so greeting deduced as std::string
    auto title { "Manager"sv }; // "Manager"sv is std::string_view literal, so title deduced as std::string_view

    return 0;
}

But in such cases, it may be better to not use type deduction.

Type Deduction and constexpr

Because constexpr isn't part of the type system, it cannot be deduced as part of type deduction. However, a constexpr variable is implicitly const, and this const will be dropped during type deduction (and can be readded if desired):

int main()
{
    constexpr double acceleration { 9.8 }; // acceleration has type const double (constexpr not part of type, const is implicit)

    auto velocity { acceleration }; // velocity has type double (const dropped)
    const auto speed { acceleration }; // speed has type const double (const dropped but reapplied)
    constexpr auto gravity { acceleration }; // gravity has type const double (const dropped but implicitly reapplied by constexpr)

    return 0;
}

Type Deduction Benefits and Downsides

Type deduction is not only convenient but also has other benefits.

First, if two or more variables are defined on sequential lines, the variable names will line up, helping to increase readability:

// harder to read
int count { 42 };
double ratio { 3.5 };

// easier to read
auto x { 42 };
auto y { 3.5 };

Second, type deduction only works on variables with initializers, so if you're in the habit of using type deduction, it can help avoid unintentionally uninitialized variables:

int x; // oops, we forgot to initialize x, compiler may not complain
auto y; // compiler will error because it can't deduce y's type

Third, you're guaranteed there will be no unintended performance-impacting conversions:

std::string_view getText(); // some function returning std::string_view

std::string text1 { getText() }; // expensive conversion from std::string_view to std::string (assuming you didn't want this)
auto text2 { getText() }; // no conversion required

Type deduction also has downsides.

First, type deduction obscures an object's type information in the code. Although a good IDE should show you the deduced type (e.g., when hovering a variable), it's still slightly easier to make type-based mistakes when using type deduction.

Example:

auto score { 95 }; // oops, we wanted a double here but accidentally provided an int literal

In the above code, if we'd explicitly specified score as type double, score would have been a double even though we accidentally provided an int literal initializer. With type deduction, score will be deduced as type int.

Here's another example:

#include <iostream>

int main()
{
     auto numerator { 15 };
     auto denominator { 4 };

     std::cout << numerator / denominator << '\n'; // oops, we wanted floating-point division

     return 0;
}

In this example, it's less clear that we're getting integer division rather than floating-point division.

Similar cases occur when a variable is unsigned. Since we don't want to mix signed and unsigned values, explicitly knowing that a variable has an unsigned type is generally something that shouldn't be obscured.

Second, if an initializer's type changes, the type of a variable using type deduction will also change, perhaps unexpectedly. Consider:

auto total { calculateSum(10, 20) + delta };

If the return type of calculateSum changes from int to double, or delta changes from int to double, total will also change type from int to double.

Overall, the modern consensus is that type deduction is generally safe to use for objects, and doing so can help make your code more readable by de-emphasizing type information so your code's logic stands out better.

Best Practice
Use type deduction for your variables when the object's type doesn't matter.

Favor an explicit type when you require a specific type that differs from the initializer's type, or when your object is used in a context where making the type obvious is useful.

Summary

Type deduction: Feature allowing the compiler to deduce an object's type from its initializer using the auto keyword.

Requirements: Type deduction requires an initializer - cannot be used with uninitialized variables or empty initializers.

const dropping: Type deduction drops const from deduced types. If you want a const deduced variable, explicitly add const (e.g., const auto x{ value }).

constexpr compatibility: Can be combined with constexpr for compile-time constants with deduced types.

String literal caveat: String literals deduce to const char*, not std::string. Use s or sv suffixes for std::string or std::string_view.

Benefits:

  • Reduces redundancy when variable and initializer types match
  • Aligns variable names for better readability
  • Prevents uninitialized variables
  • Avoids unintended performance-impacting conversions

Downsides:

  • Obscures type information in code
  • Type-based mistakes easier to make (e.g., using int literal when double intended)
  • Variable type may change unexpectedly if initializer type changes

Modern C++ consensus favors using type deduction for local variables when the specific type isn't important, while using explicit types when type clarity matters or when requiring a specific type different from the initializer.

For Context
In future lessons, we'll continue to use explicit types instead of type deduction when we feel showing the type information is helpful to understanding a concept or example.