Lesson 11: Handling Types in C++ Basics

The “C++ Basics” series of blogs serves as a reference to the book “C++ Primer (5th Edition)” (C++11 Standard), which includes my own study notes.

1. Type Aliases

Type aliases are names that serve as synonyms for certain types.

There are many benefits to using type aliases; they simplify complex type names, making them clearer and easier to understand and use, and they help programmers clearly understand the true purpose of using that type.

There are two methods to define type aliases.

1.1. typedef

The first method is to use the keyword <span>typedef</span>, which is a more traditional approach.

typedef double wages; // wages is a synonym for double
typedef wages base,*p; // base is a synonym for double, p is a synonym for double*

1.2. Alias Declaration

The second method is to use alias declaration, which is a new method specified in C++11. This method starts with the keyword <span>using</span>, followed by the alias and an equals sign.

using SI=Sales_item; // SI is a synonym for Sales_item

1.3. Pointers, Constants, and Type Aliases

typedef char *pstring;
const pstring cstr=0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char

⚠️ Here, <span>const pstring cstr=0;</span> should not be understood as <span>const char* cstr=0;</span>. The former declares a constant pointer, which is a top-level const; the latter declares a pointer to a const char, which is a bottom-level const.

2. auto Type Specifier

In programming, it is often necessary to assign the value of an expression to a variable, which requires knowing the type of the expression at the time of variable declaration. However, this is not always easy, and sometimes it is impossible. To solve this problem, the new C++11 standard introduced the <span>auto</span> type specifier.

<span>auto</span> allows the compiler to deduce the variable’s type from its initializer.

❗️ Variables defined with <span>auto</span> must have an initializer.

// The result of adding val1 and val2 can infer the type of item
auto item=val1+val2; // item is initialized to the result of val1 and val2

For example, if val1 and val2 are both of type double, then the type of item is double.

Using <span>auto</span> also allows declaring multiple variables in a single statement. Since a declaration statement can only have one base data type, all variables in that statement must have the same initial base data type:

auto i=0,*p=&i; // Correct: i is an integer, p is an integer pointer
auto sz=0,pi=3.14; // Error: sz and pi have inconsistent types

2.1. Composite Types, Constants, and auto

The type deduced by the compiler for <span>auto</span> may not always match the type of the initializer; the compiler may adjust the resulting type to better conform to initialization rules.

👉 First, using a reference actually uses the referenced object, especially when the reference is used as an initializer; the actual value participating in the initialization is the value of the referenced object. In this case, the compiler uses the type of the referenced object as the type of <span>auto</span>:

int i=0,&r=i;
auto a=r; // Since r is int&, a's type is int

👉 Secondly, <span>auto</span> generally ignores top-level const, while bottom-level const is preserved. For example, when the initializer is a pointer to a constant:

const int ci=i,&cr=ci;
auto b=ci; // b is an integer (top-level const of ci is ignored)
auto c=cr; // c is an integer (cr is an alias for ci, which is a top-level const)
auto d=&i; // d is an integer pointer
auto e=&ci; // e is a pointer to a constant integer (⚠️ taking the address of a constant object is a bottom-level const), e's type is const int*, bottom-level const is preserved.

If you want the deduced <span>auto</span> type to be a top-level const, you need to specify it explicitly:

const auto f=ci; // The deduced type of ci is int, f is const int

You can also set the type of a reference to <span>auto</span>, in which case the original initialization rules still apply:

auto &g=ci; // g is a reference to a constant integer, bound to ci
auto &h=42; // Error: cannot bind a non-constant reference to a literal
const auto &j=42; // Correct: can bind a constant reference to a literal

❗️ In the above code, auto can deduce g’s type as const int, but cannot deduce h’s type as const int because 42 is an rvalue, and ordinary references cannot bind to rvalues.

To define multiple variables in a single statement, remember that the symbols <span>&</span> and <span>*</span> only belong to a specific declarator, not to the base data type, so the initializers must be of the same type:

auto k=ci,&l=i; // k is an integer, l is an integer reference
auto &m=ci,*p=&ci; // m is a reference to a constant integer, p is a pointer to a constant integer
auto &n=i,*p2=&ci; // Error: i's type is int while &ci's type is const int

3. decltype Type Specifier

The <span>decltype</span> was introduced in the C++11 standard, which selects and returns the data type of the operand.

decltype(f()) sum=x; // sum's type is the return type of function f

❗️ The difference between <span>decltype</span> and <span>auto</span>:

  • <span>auto i = f();</span> deduces i’s data type based on the type of f()’s value, and initializes variable i with the value of expression f().
  • <span>decltype(f()) i=x;</span> also deduces i’s data type based on the type of f()’s value. However, it does not initialize variable i with the value of expression f(), but initializes it with another value x.

❗️ The way <span>decltype</span> handles top-level const and references is slightly different from <span>auto</span>.

const int ci=0,&cj=ci;
decltype(ci) x=0; // x's type is const int
decltype(cj) y=x; // y's type is const int&, y is bound to variable x
decltype(cj) z; // Error: z is a reference and must be initialized

3.1. decltype and References

If the expression used with <span>decltype</span> is not a variable, then <span>decltype</span> returns the type corresponding to the result of the expression:

int i=42,*p=&i,&r=i;
decltype(r+0) b; // Correct: the result of addition is int, so b is an (uninitialized) int
decltype(*p) c; // Error: c is int&, must be initialized

⚠️ <span>decltype(*p)</span> returns the type as int&, not int. If the content of the expression is a dereference operation, then <span>decltype</span> will yield a reference type.

❗️ For expressions used with <span>decltype</span>, if a variable name is enclosed in parentheses, the resulting type will differ from when it is not enclosed. If <span>decltype</span> uses a variable without parentheses, the result is the type of that variable; if the variable is enclosed in one or more layers of parentheses, the compiler treats it as an expression. Variables are a special kind of expression that can serve as the left-hand side of an assignment statement, so such <span>decltype</span> will yield a reference type:

// If the decltype expression is a variable enclosed in parentheses, the result will be a reference
decltype((i)) d; // Error: d is int&, must be initialized
decltype(i) e; // Correct: e is an (uninitialized) int

【Remember:】<span>decltype((variable))</span> (note the double parentheses) will always yield a reference, while <span>decltype(variable)</span> will yield a reference only if <span>variable</span> itself is a reference.

⚠️ However, it is important to note that if the expression following <span>decltype</span> is not a variable but an expression, the return type will be the type of the value computed by the expression. For example, <span>decltype((val1+val2)) a;</span> will yield int if both val1 and val2 are of type int, rather than int&. This is equivalent to <span>decltype(val1+val2) a;</span>.

🔥 Click “Read Original” to jump to my personal blog for a better reading experience!

Leave a Comment