Variables and Basic Types

C++ keywords & alternative operator names:

alignas alignof and and_eq asm 
auto bitand bitor bool break
case catch char char8_t char16_t
char32_t class compl concept const 
const_cast consteval constexpr constinit continue 
co_await co_return co_yield decltype default
delete do double dynamic_cast else 
enum explicit export extern false
float for friend goto if 
inline int long mutable namespace 
new noexcept not not_eq nullptr 
operator or or_eq private protected 
public register reinterpret_cast requires return 
short signed sizeof static static_assert
static_cast struct switch template this
thread_local throw true try typedef 
typeid typename union unsigned using 
declaration using directive virtual void 
volatile wchar_t while xor xor_eq

Compound Type

A declaration is a base type followed by a list of declarators.

References

When we define a reference, we bind the reference to its initializer. There is no way to rebind a reference to refer to a different object.

int i = 0;
int& r1 = i; // Legal but might be misleading.The declarator should be put with the variable name.
int &r2 = i; // Good.

Two exceptions to the rule that the type of a reference must match the type of the object to which it refers:

  • We can initialize a reference to const from any expression that can be converted

Pointers

Null pointers:

int *p1 = nullptr; // Equivalent to int *p1 = 0;
int *p2 = 0; // Directly initializes p2 from the literal constant 0.

int *p3 = NULL; // Equivalent to int *p3 = 0; Must #include <cstdlib>

A void* pointer holds an address, but the type of the object at that address is unknown.

Discussion

// i is an int; p is a pointer to int; r is a reference to int 
int i = 1024, *p = &i, &r = i;

A reference is not an object such that we may not have a pointer to a reference.

A pointer is an object, we can define a reference to a pointer.

int *p; // p is a pointer to int
int *&r = p; // r is a reference to the pointer p

Suggestion: understand pointer or reference declarations by reading from right to left.

const Qualifier

Because we can’t change the value of a const object after we create it, it must be initialized.

By default, const objects are local to a file.

To define a single instance of a const variable that can be shared acrossed files, we use the keyword extern on both its definition and declaration(s):

// In file_1.cc defines and initializes a const that is accessible to other files.
extern const int bufSize = fcn();
// In file_1.h
extern const int bufSize; // Same bufSize as defined in file_1.cc

References to const

const int ci = 1024;
const int &r1 =  ci; // OK: both reference and underlying object are const
r1 = 42; // ERROR: r1 is a reference to const
int &r2 = ci; // ERROR: nonconst reference to a const object

Because we cannot assign directly to ci, we also should not be able to use a reference to change ci.

reference to const == const reference

We can initialize a reference to const from any expression that can be converted

double dval = 3.14;
const int &ri = dval;

The code is transdformed by the compiler into:

const int temp = dval;
const int &ri = temp;

A reference to const restricts only what we can do through that reference. Binding a reference to const to an object says nothing about whether the underlying object itself is const.

Pointers and const

const double pi = 3.14; // pi is const; its value may not be changed 
double *ptr = &pi; // Error: ptr is a plain pointer
const double *cptr = &pi; // Ok:cptr may point to a double that is const
*cptr = 42; // Error: cannot assign to *cptr

const Pointers

const pointer != pointer to const

Like any other const object, a const pointer must be initialized, and once initialized, its value (i.e., the address that it holds) may not be changed.

int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb 
const double pi = 3.14159;
const double *const pip = &pi; // pip is a const pointer to a const object

Top-Level const

We use the term top-level const to indicate that the pointer itself is a const. When a pointer can point to a const object, we refer to that const as a low-level const.

const int *p1; // This is a fucking low-level const.
int const* p2; // This is a fucking top-level const.
int *const p2; // This is also a fucking top-level const.

constexpr and Constant Expressions

A constant expression is an expression whose value cannot change and that can be evaluated at compile time.

const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression

Under the new standard, we can ask the compiler to verify that a variable is a constant expression by declaring the variable in a constexpr declaration. Variables declared as constexpr are implicitly const and must be initialized by constant expressions.

constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression 
constexpr int sz = size(); // OK only if size is a constexpr function

The types we can use in a constexpr are known as “literal types” because they are simple enough to have literal values.

We can initialize a constexpr pointer from the nullptr literal or the literal (i.e., constant expression) 0. We can also point to (or bind to) an object that remains at a fixed address (an object defined outside of any function).

constexpr int *np = nullptr; // np is a constant pointer to int that is null 
int j = 0;
constexpr int i = 42; // type of i is const int
// i and j must be defined outside any function
constexpr const int *p = &i; // p is a constant pointer to the const int i 
constexpr int *p1 = &j; // p1 is a constant pointer to the int j

It is important to understand that when we define a pointer in a constexpr declaration, the constexpr specifier applies to the pointer, not the type to which the pointer points:

const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int

Dealing with Types

Type Aliases

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

The new standard introduced a second way to define a type alias, via an alias declaration:

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

**Do not intepreting a declaration that uses a type alias by conceptually replacing the alias with its corresponding type. **

typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char, char * is the base type
const char *cstr=0; // the base type is const char

The auto Type Specifier

auto i = 0, *p = &i; // ok:i is int and p is a pointer toint

Compound Types, const and auto

int i = 0, &r = i;
auto a = r; // a is an int(r is an alias fori, which has type int)

auto ordinarily ignores top-level consts.

const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level) 
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int* (& of a const object is low-level const)

If we want the deduced type to have a top-level const, we must say so explicitly:

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

We can also specify that we want a reference to the auto-deduced type. When we ask for a reference to an auto-deduced type, top-level consts in the initializer are not ignored.

auto &g = ci; // g is a const int& that is bound to ci 
auto &h = 42; // error: we can’t bind a plain reference to a literal 
const auto &j = 42; // ok: we can bind a const reference to a literal

The decltype Type Specifier

decltype(f()) sum = x; // sum has whatever type f returns

Here, the compiler does not call f, but it uses the type that such a call would return as the type for sum.

When the expression to which we apply decltype is a variable, decltype returns the type of that variable, including top-level const and reference:

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

decltype and References

Generally speaking, decltype returns a reference type for expressions that yield objects that can stand on the left-hand side of the assignment.

The above rule only apply to EXPRESSIONs. For example, i is an variable while i+0 and (i) are expressions.

// decltype of an expression can be a reference type
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int 
decltype(*p) c; // error: c is int& and must be initialized

The dereference operator is an example of an expression for which decltype returns a reference.

Last updated