The Const and Constexpr in C++ (REMAKE)

I have written a post about const in C++ in Chinese before, but it was too verbose and unclear. :( So, I decided to rewrite it.

BASIC USAGE OF CONST

MODIFIES BASIC DATE TYPE WITH CONST

The most basic usage of const is to define a constant. A constant is a variable whose value cannot be changed after initialization.

1
2
3
4
5
6
int a = 1;
const double b = 1.0; // now b is immutable

a = 2; // a is a variable. It's okay.
// b = 2.0; // WRONG! b is a constant.

MODIFIES POINTERS WITH CONST

When it comes to pointers, const can be used in two ways: top-level const and low-level const.

Top-level const means that the pointer itself is a constant, i.e. you can't change what it points to.
Low-level const means that the object that the pointer points to is a constant, i.e. you can't change the value of that object through this pointer.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int x = 1;
int y = 2;
int *const p1 = &x; // top-level const
const int *p2 = &x; // low-level const
const int *const p3 = &x; // The first is low-level
// The second is top-level

// p1 = &y; // WRONG! p1 is a constant pointer, it can't change its direction.
*p1 = y; // OK

p2 = &y; // OK
// *p2 = y; // WRONG! p2 points at a constant.

// p3 = &y; // WRONG!
// *p3 = y; // WRONG!

MODIFIES REFERENCE WITH CONST

For reference, there is only one way to use const: const T & (T is type name). Reference itself (like T &) is always top-level, since you can't change what it refers to after its initialization. So, const T & makes the reference both top-level and low-level.

MODIFIES OBJECTS WITH CONST

Modifying an object with const means that this object is immutable after its initialization.

1
const std::string str = "This is an immutable string.";

CONST IN FUNCTIONS

CONST IN PARAMETER LIST

A parameter modified with const means it is read-only to the function. Usually, we use this feature together with reference. So, when we don't want a function to change a specific argument, the parameter's type may look like const T &.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <string>

template <typename T>
void Show(const T &);

int main() {
std::string str = "This is an example.";
Show(str);
return 0;
}

template <typename T>
void Show(const T &in) {
// in is read-only
std::cout << in << std::endl;
}

THE CONST AFTER A MEMBER FUNCTION

The const after a member function indicates that the function does not modify any non-mutable data members of the class. In other words, this means that the this pointer is const, implying that this member function does not alter the state of this object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

class Fish {
private:
double weight_;

public:
Fish() : weight_(0) {}
Fish(const double& weight) : weight_(weight) {}
void ModifyWeight(const double& weight);
void ShowWeight() const; // Just show the weight. Read-only.
};

void Fish::ModifyWeight(const double& weight) { this->weight_ = weight; }
void Fish::ShowWeight() const { std::cout << this->weight_ << std::endl; }

int main() {
Fish fish(5.0);
fish.ShowWeight();
fish.ModifyWeight(10.0);
fish.ShowWeight();
return 0;
}

RETURNS A CONST

When returning an object, the const before the return type indicates that this object is a constant and is immutable. For example: const int& GetAgeConst(), which returns a rvalue whose referenced content cannot be modified.

When return a pointer type (or a reference), const helps protect the pointer or reference content from being modified. For example: const char * const Fun(), which returns a pointer to a constant, whose pointed content and the pointer itself cannot be modified.

Here is an example generated by new bing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

class Person {
private:
int age;

public:
Person(int a) : age(a) {}
const int& GetAgeConst() const { return age; }
};

int main() {
Person p(20);
cout << "The age is " << p.GetAgeConst() << endl;
// p.GetAgeConst() = 30; // WRONG
return 0;
}

CONSTEXPR (NEW IN C++11)

constexpr keyword helps the compiler find those constant expressions at compile stage. A really common place of use is defining an array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

constexpr int GetConstexprLen();
constexpr int Fibonacci(const int n);

int main() {
int variable_len = 10;
const int const_len = 10;
constexpr int constexpr_len = 10;

// int arr1[variable_len]; // Illegal. Not OK.
// int arr2[const_len]; // Illegal. But usually OK.
int arr3[constexpr_len]; // Legal. OK.
int arr4[GetConstexprLen()]; // Legal. OK.
int arr5[Fibonacci(5)]; // Legal. OK.
return 0;
}

constexpr int GetConstexprLen() { return 10; }
constexpr int Fibonacci(const int n) {
// These codes are from https://changkun.de/modern-cpp/

// You can't use local variables, loops,
// and conditional statements here in C++11
// But in C++14, that's OK.
return n == 1 || n == 2 ? 1 : Fibonacci(n - 1) + Fibonacci(n - 2);
}

To learn more, you may read Modern C++ Tutorial