BlockLune's Blog

Home Tags About

The const and constexpr in C++ (REMAKE)

published at 2023-03-24
c & c++ const

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