Skip to main content

Basics

When writing #include "lib-custom.h", the compiler checks the CWD first, then the includes directory, and will check system includes last.

When we write #include <iostream>, the compiler checks the includes directory first, then system includes.

An lvalue is any value that has a location in memory. These can also be viewed as any value that is accessible in more than one place anywhere within your code. These could be named objects, pointers, or references. A general rule of thumb: if you can take it's address, it is an lvalue.

An rvalue refers to objects that are only accessible at one exact location within your code. These could be temporary objects like by-value function return values, a collection of operations wrapped in parenthesis that is substituted as the value of a new assignment, literal constants like 1, 10, 'c', or a "string-literal". A general rule of thumb is if it is not an lvalue, it's an rvalue

The definition of these terms provide context for legal and illegal operations in C++. For example, the following statements are legal

int i = 0;
++i = 55 + 5;

But the following statement is not legal, since in this context i++ is not an lvalue. That is, i++ doesn't have a location in memory until after the increment is applied, which makes this assignment invalid.

i++ = 55;

Some examples of lvalues and rvalues in C++

int x;
x = 10; // x is an lvalue; 10 is an rvalue

int sizeDiff(const int &a, const int &b); // sizeDiff, a, and b are all lvalues; The int that is returned by sizeDiff, is an rvalue

Combining the two statements described above, we can better understand an assignment operation

int v = 0; // v and s are both lvalues
int s = 5; // 0 and 5 are both rvalues

int *px = sizeDiff(v, s); // *px is an lvalue; sizeDiff(v, s) is an rvalue 

An expression is a mechanism for generating new values. May or may not contain operators, constants, variables;

Literal constant is a value that is stated literally, without representation through a variable.

Constants are variables defined as const given a type, name, and value;

Const qualifiers are easiest when read right-to-left. For example, consider the following declarations, where we look at differences in const values or pointers. If a value is const, it cannot be changed. If a pointer is const, the location in memory that stores the data cannot be changed. A const reference is considered undefined behavior, but a reference to a const value is permitted, and often used to avoid the unnecessary copying of data.

We should notice in the examples below that we cannot assign a const value to a reference or pointer to non-const data

int const x = 5; // A constant integer x
const int x = 5; // Also a constant integer x

int const & a = x; // Valid, a is a reference to the const value stored at the memory location of x
const int & b = x; // Valid, b is a reference to the const value stored at the memory location of x (Same as above)
int & c = x; // Error! Cannot assign a reference of const data(x) to reference to non-const data(c)

int const * d = &x; // Valid, d is a non-const pointer to the const data stored at the memory location of x
const int * e = &x; // Valid, e is a non-const pointer to the const data stored at the memory location of x (Same as above)
int * f = &x; // Error! Cannot assign pointer with non-const data to a reference with const data
int const * const g = &x; // Valid, g is a const pointer to const data stored at the memory location of x
const int * const h = &x; // Valid, h is a const pointer to const data stored at the memory location of x (Same as above)

When dealing with non-const data, the rules are slightly different. We should notice in the examples below that we can assign a non-const value to a reference or pointer to const data

int y = 10; // A non-const integer y

int * i = &y; // Valid, i is a non-const pointer to non-const data stored at the memory location of y
int * const j = &y; // Valid, j is a const pointer to non-const data stored at the memory location of y
int & k = y; // Valid, k is a reference to non-const data stored at the memory location of y

// With the below declarations, we add the const qualifier to previously non-const data
// This makes the value const when we attempt to access it through d, but y is still non-const to those who are within it's scope and able to access it.
int const & l = y; // Valid, l is a reference to const data stored at the memory location of y
const int & m = y; // Valid, m is a reference to const data stored at the memory location of y

// Any declaration with a const reference like those seen below is considered to be unspecified
// If you can find a compiler that lets this happen, the results can vary wildly
int & const n = y; // Error! Unspecified behavior when applying const to reference
int const & const o = y; // Error! Unspecified behavior when applying const to reference
const int & const p = y; // Error! Unspecified behavior when applying const to reference

Array initialization can be done using any one of the examples below

int array[10]; // All values in array are initialized to an undetermined (arbitrary) value
int arr[10] = {1, 2, 3, 4}; // arr[0] = 1, arr[1] = 2, arr[2] = 3, arr[3] = 4, arr[4] = 0, arr[5] = 0...
int a[10] = { }; // a[0] = 0, a[1] = 0, a[2] = 0, ... , a[9] = 0
int a[5] = {2}; // a[0] = 2, a[1] = 0, ... , a[4] = 0
for (auto &e : a) e = 1; // a[0] = 1, ... , a[4] = 1
for (auto e : a) std::cout << --e << std::endl; // 0 0 0 0 0
for (auto &e : a) std::cout << e++ << std::endl; // 1 1 1 1 1
for (auto e : a) std::cout << e << std::endl; // 2 2 2 2 2