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
const
QualifierBecause 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
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
const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = π // Error: ptr is a plain pointer
const double *cptr = π // 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 = π // pip is a const pointer to a const object
Top-Level const
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
constexpr
and Constant ExpressionsA 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
Type Specifierdecltype(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