const and constexpr in C++
Published at 2023-03-24
Last update over 365 days ago
Licensed under CC BY-NC-SA 4.0
cpp
programming-language
const-usage
constexpr
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.
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:
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.
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 &
.
#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.
#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:
#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:
#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