We previously discussed how the auto keyword enables the compiler to deduce a variable's type from its initializer:

int main()
{
    int num{ 10 };
    auto copy{ num }; // copy deduced as an int

    return 0;
}

We also noted that by default, type deduction drops const from types:

int main()
{
    const double pi{ 3.14159 }; // pi has type const double
    auto value{ pi };           // value has type double (const dropped)

    constexpr double e{ 2.71828 }; // e has type const double (constexpr implicitly applies const)
    auto number{ e };              // number has type double (const dropped)

    return 0;
}

Const (or constexpr) can be reapplied by adding the const (or constexpr) qualifier to the deduced type's definition:

int main()
{
    double value{ 2.5 };     // value has type double
    const auto locked{ value };  // locked has type const double (const applied)

    constexpr double pi{ 3.14159 }; // pi has type const double (constexpr implicitly applies const)
    const auto copy{ pi };          // copy is const double (const dropped, const reapplied)
    constexpr auto fixed{ pi };     // fixed is constexpr double (const dropped, constexpr reapplied)

    return 0;
}

Type deduction drops references

In addition to dropping const, type deduction also drops references:

#include <string>

std::string& getText(); // some function that returns a reference

int main()
{
    auto message{ getText() }; // type deduced as std::string (not std::string&)

    return 0;
}

In this example, variable message uses type deduction. Although function getText() returns a std::string&, the reference qualifier is dropped, so message's type is deduced as std::string.

Just like with dropped const, if you want the deduced type to be a reference, you can reapply the reference at the point of definition:

#include <string>

std::string& getText(); // some function that returns a reference

int main()
{
    auto message1{ getText() };  // std::string (reference dropped)
    auto& message2{ getText() }; // std::string& (reference dropped, reference reapplied)

    return 0;
}

Top-level const and low-level const

A top-level const is a const qualifier that applies to an object itself. For example:

const int num;      // this const applies to num, so it is top-level
int* const pointer; // this const applies to pointer, so it is top-level
// references don't have a top-level const syntax, as they are implicitly top-level const

In contrast, a low-level const is a const qualifier that applies to the object being referenced or pointed to:

const int& reference; // this const applies to the object being referenced, so it is low-level
const int* pointer;   // this const applies to the object being pointed to, so it is low-level

A reference to a const value is always a low-level const. A pointer can have a top-level, low-level, or both kinds of const:

const int* const pointer; // the left const is low-level, the right const is top-level

When we say that type deduction drops const qualifiers, it only drops top-level consts. Low-level consts are not dropped. We'll see examples momentarily.

Type deduction and const references

If the initializer is a reference to const, the reference is dropped first (and then reapplied if applicable), and then any top-level const is dropped from the result.

#include <string>

const std::string& getTextConstRef(); // some function that returns a reference to const

int main()
{
    auto message1{ getTextConstRef() }; // std::string (reference dropped, then top-level const dropped from result)

    return 0;
}

In this example, since getTextConstRef() returns a const std::string&, the reference is dropped first, leaving us with a const std::string. This const is now a top-level const, so it is also dropped, leaving the deduced type as std::string.

Key Concept
Dropping a reference may change a low-level const to a top-level const: `const std::string&` is a low-level const, but dropping the reference yields `const std::string`, which is a top-level const.

We can reapply a reference and/or const:

```cpp #include

const std::string& getTextConstRef(); // some function that returns a const reference

int main() { auto message1{ getTextConstRef() }; // std::string (reference and top-level const dropped) const auto message2{ getTextConstRef() }; // const std::string (reference dropped, const dropped, const reapplied)

auto& message3{ getTextConstRef() };       // const std::string& (reference dropped and reapplied, low-level const not dropped)
const auto& message4{ getTextConstRef() }; // const std::string& (reference dropped and reapplied, low-level const not dropped)

return 0;

}


We covered the case for `message1` in the prior example. For `message2`, this is similar to the `message1` case, except we're reapplying the `const` qualifier, so the deduced type is `const std::string`.

Things get more interesting with `message3`. Normally the reference would be dropped first, but since we've reapplied the reference, it is not dropped. That means the type is still `const std::string&`. And since this const is a low-level const, it is not dropped. Thus the deduced type is `const std::string&`.

The `message4` case works similarly to `message3`, except we've reapplied the `const` qualifier as well. Since the type is already deduced as a reference to const, us reapplying `const` here is redundant. That said, using `const` here makes it explicitly clear that our result will be const (whereas in the `message3` case, the constness of the result is implicit and not obvious).

<div class="best-practice">
<strong>Best Practice</strong><br>
If you want a const reference, reapply the `const` qualifier even when it's not strictly necessary, as it makes your intent clear and helps prevent mistakes.

</div>
## What about constexpr references?

Constexpr is not part of an expression's type, so it is not deduced by `auto`.

A reminder

When defining a const reference (e.g. `const int&`), the const applies to the object being referenced, not the reference itself.

When defining a constexpr reference to a const variable (e.g. `constexpr const int&`), we need to apply both `constexpr` (which applies to the reference) and `const` (which applies to the type being referenced).

```cpp
#include <string_view>
#include <iostream>

constexpr std::string_view greeting{ "Welcome" };   // implicitly const

constexpr const std::string_view& getGreetingRef() // function is constexpr, returns a const std::string_view&
{
    return greeting;
}

int main()
{
    auto message1{ getGreetingRef() };                  // std::string_view (reference dropped and top-level const dropped)
    constexpr auto message2{ getGreetingRef() };        // constexpr const std::string_view (reference dropped and top-level const dropped, constexpr applied, implicitly const)

    auto& message3{ getGreetingRef() };                 // const std::string_view& (reference reapplied, low-level const not dropped)
    constexpr const auto& message4{ getGreetingRef() }; // constexpr const std::string_view& (reference reapplied, low-level const not dropped, constexpr applied)

    return 0;
}

Type deduction and pointers

Unlike references, type deduction does not drop pointers:

#include <string>

std::string* getPointer(); // some function that returns a pointer

int main()
{
    auto pointer1{ getPointer() }; // std::string*

    return 0;
}

We can also use an asterisk in conjunction with pointer type deduction (auto*) to make it clearer that the deduced type is a pointer:

#include <string>

std::string* getPointer(); // some function that returns a pointer

int main()
{
    auto pointer1{ getPointer() };  // std::string*
    auto* pointer2{ getPointer() }; // std::string*

    return 0;
}
Key Concept
The reason that references are dropped during type deduction but pointers are not dropped is because references and pointers have different semantics.

When we evaluate a reference, we're really evaluating the object being referenced. Therefore, when deducing a type, it makes sense that we should deduce the type of the thing being referenced, not the reference itself. Also, since we deduce a non-reference, it's really easy to make it a reference by using auto&. If type deduction were to deduce a reference instead, the syntax for removing a reference if we didn't want it is much more complicated.

On the other hand, pointers hold the address of an object. When we evaluate a pointer, we are evaluating the pointer, not the object being pointed to (if we want that, we can dereference the pointer). Therefore, it makes sense that we should deduce the type of the pointer, not the thing being pointed to.

## The difference between auto and auto* Optional

When we use auto with a pointer type initializer, the type deduced for auto includes the pointer. So for pointer1 above, the type substituted for auto is std::string*.

When we use auto* with a pointer type initializer, the type deduced for auto does not include the pointer—the pointer is reapplied afterward after the type is deduced. So for pointer2 above, the type substituted for auto is std::string, and then the pointer is reapplied.

In most cases, the practical effect is the same (pointer1 and pointer2 both deduce to std::string* in the above example).

However, there are a couple of difference between auto and auto* in practice. First, auto* must resolve to a pointer initializer, otherwise a compile error will result:

#include <string>

std::string* getPointer(); // some function that returns a pointer

int main()
{
    auto pointer3{ *getPointer() };      // std::string (because we dereferenced getPointer())
    auto* pointer4{ *getPointer() };     // does not compile (initializer not a pointer)

    return 0;
}

This makes sense: in the pointer4 case, auto deduces to std::string, then the pointer is reapplied. Thus pointer4 has type std::string*, and we can't initialize a std::string* with an initializer that is not a pointer.

Second, there are differences in how auto and auto* behave when we introduce const into the equation. We'll cover this below.

Type deduction and const pointers Optional

Since pointers aren't dropped, we don't have to worry about that. But with pointers, we have both the const pointer and the pointer to const cases to think about, and we also have auto vs auto*. Just like with references, only top-level const is dropped during pointer type deduction.

Let's start with a simple case:

#include <string>

std::string* getPointer(); // some function that returns a pointer

int main()
{
    const auto pointer1{ getPointer() };  // std::string* const
    auto const pointer2 { getPointer() }; // std::string* const

    const auto* pointer3{ getPointer() }; // const std::string*
    auto* const pointer4{ getPointer() }; // std::string* const

    return 0;
}

When we use either auto const or const auto, we're saying, "make the deduced pointer a const pointer". So in the case of pointer1 and pointer2, the deduced type is std::string*, and then const is applied, making the final type std::string* const. This is similar to how const int and int const mean the same thing.

However, when we use auto*, the order of the const qualifier matters. A const on the left means "make the deduced pointer a pointer to const", whereas a const on the right means "make the deduced pointer type a const pointer". Thus pointer3 ends up as a pointer to const, and pointer4 ends up as a const pointer.

Now let's look at an example where the initializer is a const pointer to const.

#include <string>

int main()
{
    std::string text{};
    const std::string* const pointer { &text };

    auto pointer1{ pointer };  // const std::string*
    auto* pointer2{ pointer }; // const std::string*

    auto const pointer3{ pointer };  // const std::string* const
    const auto pointer4{ pointer };  // const std::string* const

    auto* const pointer5{ pointer }; // const std::string* const
    const auto* pointer6{ pointer }; // const std::string*

    const auto const pointer7{ pointer };  // error: const qualifier can not be applied twice
    const auto* const pointer8{ pointer }; // const std::string* const

    return 0;
}

The pointer1 and pointer2 cases are straightforward. The top-level const (the const on the pointer itself) is dropped. The low-level const on the object being pointed to is not dropped. So in both cases, the final type is const std::string*.

The pointer3 and pointer4 cases are also straightforward. The top-level const is dropped, but we're reapplying it. The low-level const on the object being pointed to is not dropped. So in both cases, the final type is const std::string* const.

The pointer5 and pointer6 cases are analogous to the cases we showed in the prior example. In both cases, the top-level const is dropped. For pointer5, the auto* const reapplies the top-level const, so the final type is const std::string* const. For pointer6, the const auto* applies const to the type being pointed to (which in this case was already const), so the final type is const std::string*.

In the pointer7 case, we're applying the const qualifier twice, which is disallowed, and will cause a compile error.

And finally, in the pointer8 case, we're applying const on both sides of the pointer (which is allowed since auto* must be a pointer type), so the resulting types is const std::string* const.

Best Practice
If you want a const pointer, pointer to const, or const pointer to const, reapply the `const` qualifier(s) even when it's not strictly necessary, as it makes your intent clear and helps prevent mistakes.
Tip
Consider using `auto*` when deducing a pointer type. Using `auto*` in this case makes it clearer that we are deducing a pointer type, enlists the compiler's help to ensure we don't deduce a non-pointer type, and gives you more control over const.
## Summary

Sorry to hear about your headache. Let's recap the most important points quickly.

Top-level vs low-level const:

  • A top-level const applies to the object itself (e.g. const int num or int* const pointer).
  • A low-level const applies to the object accessed through a reference or pointer (e.g. const int& reference, const int* pointer).

What type deduction deduces:

  • Type deduction first drops any references (unless the deduced type is defined as a reference). For a const reference, dropping the reference will cause the (low-level) const to become a top-level const.
  • Type deduction then drops any top-level const (unless the deduced type is defined as const or constexpr).
  • Constexpr is not part of the type system, so is never deduced. It must always be explicitly applied to the deduced type.
  • Type deduction does not drop pointers.
  • Always explicitly define the deduced type as a reference, const, or constexpr (as applicable), and even if these qualifiers are redundant because they would be deduced. This helps prevent errors and makes it clear what your intent is.

Type deduction and pointers:

  • When using auto, the deduced type will be a pointer only if the initializer is a pointer. When using auto*, the deduced type is always a pointer, even if the initializer is not a pointer.
  • auto const and const auto both make the deduced pointer a const pointer. There is no way to explicitly specify a low-level const (pointer-to-const) using auto.
  • auto* const also makes the deduced pointer a const pointer. const auto* makes the deduced pointer a pointer-to-const. If these are hard to remember, int* const is a const pointer (to int), so auto* const must be a const pointer. const int* is a pointer-to-const (int), so const auto* must be a pointer-to-const)
  • Consider using auto* over auto when deducing a pointer type, as it allows you to explicitly reapply both the top-level and low-level const, and will error if a pointer type is not deduced.