Item 2: Understand auto type deduction.

Posted by jamesmage on Sat, 29 Jan 2022 13:59:40 +0100

This is for Effective Modern C++ Study notes for Item2.

This time, you can learn the automatic derivation of auto type. Except for one scenario, the template type derivation method learned in Item1 can be tried. Review the type derivation of the template:

template<typename T>
void f(ParamType param);

f(expr); // call f with some expression

At the call point f, the compiler uses expr to derive the types of T and ParamType.

For variables declared with auto, auto corresponds to T, and the type descriptor of the variable corresponds to ParamType. In the following example, the type descriptor of rx is const Auto &, and x is understood as the parameter expr of function f call.

const auto& rx = x

The corresponding template type derivation is divided into three scenarios according to the ParamType type. The type derivation of variable declaration using auto is also divided into three scenarios according to the type descriptor of variable:

  1. Case 1: the type descriptor is a pointer or reference, but not a universal reference.
  2. Case 2: the type specifier is a universal reference.
  3. Case 3: the type specifier is neither a pointer nor a reference.

The method of Item1 is applicable to these three scenarios, as illustrated below.

Case 1: the type descriptor is a pointer or reference, but not a universal reference.

int x = 27;
const int cx = x;
const int& rcx = x;

auto& y = x;   // The type of y is int&
auto& y1 = cx;  // y1 is of type const int&
auto& y2 = rcx; // The type of y2 is const int&
auto& y3 = rcx; // The type of y2 is const int&

Case 2: the type specifier is a universal reference.

int x = 27;
const int cx = x;
const int& rcx = x;

auto&& y = 27;    // The type of y is int&&
auto&& y1 = x;    // y1 is of type int&
auto&& y2 = cx;   // The type of y2 is const int&
auto&& y3 = rcx;  // The type of y3 is const int&

Case 3: the type specifier is neither a pointer nor a reference.

int x = 27;
const int cx = x;

auto y = 27;        // The type of y is int
auto y1 = x;        // y1 is of type int
auto y2 = cx;       // The type of y2 is int
const auto y3 = x;  // The type of y3 is const int
const auto y4 = cx; // The type of y4 is const int

Item1 also discusses the degradation of array and function names into pointers, which is also applicable to the type derivation of auto:

const char name[] = "R. N. Briggs";

auto y1 = name;       // y1 type is const char*
auto& y2 = name;      // y2 type is const char (&) [13]

void someFunc(int, double);

auto f1 = someFunc;    // f1 type is void (*)(double, int)
auto& f2 = someFunc;   // Type is void (&) (int, double)

Special scenario: initialization list STD:: initial sizer_ list

For variable initialization, see the following:

int x1 = 27;
int x2(27);
int x3 = { 27 };
int x4{ 27 };

x3 and x4 are initialized in the way of initialization list. The types of x1~x4 are int. However, Item5 will explain why it is advantageous to use auto to declare a specific type of variable. Here, replace int with auto:

auto x1 = 27;     // type is int, value is 27
auto x2(27);      // type is int, value is 27
auto x3 = { 27 }; // type is std::initializer_list<int>,
                  // value is { 27 }
auto x4{ 27 };    // type is std::initializer_list<int>,
                  // value is { 27 }

x1 and x2 are still int types, but x3 and x4 are STD:: initial sizer_ List < int > type and contains an element 27. This is the special feature of variable declaration auto type derivation: when auto is used to declare a variable and the variable is initialized in braces, the type of the variable is deduced as STD:: initial sizer_ List type.

However, the following initialization methods will fail:

auto x5 = { 1, 2, 3.0 };   // error! can't deduce T for std::initializer_list<T>

Because there are actually two types of derivation here. First, the type of x5 is derived as std::initializer_list, due to std::initializer_list is a template, and then it must be std::initializer_list < T > instantiate a T, that is, t should also be deduced. There are two data types in the table, and t derivation fails.

This is the difference between auto type derivation and template type derivation. Passing such an initialization list to the template will lead to derivation failure:

template<typename T>
void f(T param);

f({ 11, 23, 9 }); // error! can't deduce type for T

However, if you specify a template, the parameter type is STD:: initializer_ List < T >, template type derivation will derive T:

template<typename T>
void f(std::initializer_list<T> initList);

f({ 11, 23, 9 });   // T deduced as int, and initList's type is std::initializer_list<int>

In Item 3, you will see that C++14 allows auto as the return value of the function and can be deduced. Then, the template derivation is used instead of auto derivation. Therefore, when auto is used as the return value of the function, it is not allowed to return a brace initialization list, and the compilation will fail:

auto createInitList()
{
   return { 1, 2, 3 };  // error: can't deduce type for { 1, 2, 3 }
}

The same is true for lambda functions:

std::vector<int> v;
...
auto resetV = [&v](const auto& newValue) { v = newValue; };  // C++14
...
resetV({ 1, 2, 3 });   // error: can't deduce type for { 1, 2, 3 }

Finally, the auto derivation is summarized as follows:

  • The auto type derivation is consistent with the template type derivation except for the brace initialization list. STD:: initializer is not supported for template type derivation_ list.
  • When the return value of the function is auto, the template derivation is actually used instead of auto type derivation.

Topics: C++