A constant is a variable which value cannot be modified through it.
Constants are not specific to classes. They do, however, have special meaning and applications in regard to classes.
## Protecting Values
They are created with the reserved keyword `const`.
```cpp
int main()
{
int const i(3);
i = 5; // 🚫 Does not compile
return 0;
}
```
However, if creating a constant guarantees against mutations through the variable name, it does not guarantee the underlying value will never be modified. Creating a pointer to that value makes it possible to manipulate it still.
C++ does have safeguards, though. The code below will not compile, because C++ prevents from initializing an `int*` (a pointer to a non-const int) with an `int const*` (the address of an `int`). This is a fundamental safety feature, the lack of which would render the `const` keyword meaningless.
```cpp
int main()
{
int const i(3);
int* ptr(&i); // 🚫 Does not compile
*ptr = 5;
return 0;
}
```
However, there is a dangerous tool called `const_cast` that allows you to remove that safety. Applying it to a const removes the `const` qualifier.
```cpp
int main()
{
int const i = 3;
int* ptr = const_cast<int*>(&i);
*ptr = 5; // The value is modified
std::cout << "i = " << i << std::endl; // Prints 5
std::cout << "*ptr = " << *ptr << std::endl; // Prints 5
return 0;
}
```
This is bad taste, of course, and creates room for undefined behavior. The only 'safe' and valid reason to use `const_cast` is to remove the `const` qualifier form a reference or pointer that was not originally declared as `const`.
## Syntax
`const` always applies to what precedes it; if nothing does, it applies to what's next.
We could rewrite our previous example to inverse `const` and `int`, which is equivalent.
```cpp
int main()
{
const int i(3);
return 0;
}
```
However, things get messier with pointers.
`const int* ptr(&i)` defines a pointer to a `const int`. It's easier to grasp if you wrap it in parentheses to understand how the compiler reads it: `(((const (int))*) ptr(&i))`. `const` is applied to `int`, a pointer is created on that and stored in a variable `ptr`.
`int const* ptr(&i)` defines a pointer to a `const int`: `(((int (const))*) ptr(&i))`. This is functionally the same thing as the example above. Because `const` is preceded by an `int`, it is applied to it. Then a pointer is created on that and stored in a variable `ptr`.
`int* const ptr(&i)` defines a constant pointer to an `int`: `((((int)*) const) ptr(&i))`. A pointer to an `int` is created. It is then made into a constant before being assigned to `ptr`.
`int const * const ptr(&i)` protects both the pointer and the address against mutation. After this, it will not be possible to create a mutable pointer towards that value from this pointer. `(((((int) const)*) const) ptr(&i))`: Both the address in memory and the pointer are made into constants here.
Alternatively, you can read those expressions from right to left.
## Class Usage
Unsurprisingly, you can make a class attributes into constants. This is useful for instance if you give an `id` to a class's instances. You don't want it to be modifiable during the object's life span.
```cpp
class Item
{
private:
const int id = 1;
};
```
You can use the `const` keyword for functions and methods as well, in three different ways.
Using it at the end of a method's signature signals the compiler and other programmers that this method does not mutate the instance internal state, and allows the method to be called on constant instances of the class. This is specific to classes.
```cpp
class Foo
{
public:
Foo()
: bar(0)
{}
read_bar() const
{
return bar;
}
private:
int bar;
};
int main()
{
const Foo baz;
const bar = baz.read_bar();
return 0;
}
```
You can protect the returned value from mutation. I imagine this would be useful for setters and getters, when they return an object by reference.
```cpp
int generate_random_id()
{
//...
}
struct Price
{
double euros;
double dollars;
};
class Item
{
public:
Item(double euro_price, double dollar_price)
: price{euro_price,dollar_price}, id(generate_random_id())
{}
// Return a const ref so callers can't mutate internal state
// Since it does not mutate state, we mark it const
const Price& check_price_in_euros() const
{
return price;
}
private:
const int id;
Price price;
};
int main()
{
Item textbook(10.2,13.7);
// Because check_price_in_euros() returns a const Price&,
// you must bind it to a const reference
const Price& price = textbook.check_price_in_euros();
// Can't mutate a constant reference
price.euros = 1.0;
return 0;
}
```
The last way we can use const is within a function's parameters list. It indicates the local copy of the received argument will not be mutated or assigned a new value. Only `const` methods that do not mutate the state on an instance will be available to us.
```cpp
void print(const std::string text)
{
std::cout << text << std::endl;
return 0;
}
```
This does not apply to the original argument we were given, however.
Using the `const` keyword when we pass arguments by reference is what makes it powerful. Creating a reference avoids making a potentially expensive copy of the string, while `const` guarantees the function will not modify the original string it received by reference.
Let's revise our previous example.
```cpp
void print(const std::string& text)
{
std::cout << text << std::endl;
return 0;
}
```
This is the idiomatic way to pass complex, read-only objects in C++.
## West Const vs East Const
It's common to write `const MyClass&`. This style is widespread and referred to as *West* `const`.
There's an alternative syntax, worth mentioning, because you might encounter it on occasions, that's equivalent to the West `const`, and is referred to as *East* `const`: `MyClass const&`. Functionally, there is no difference whatsoever between the two. The argument for this syntax is that it's more consistent because it can always be read right to left, especially with pointers.
In the end, it's really a matter of style and consistency. If you work on an existing project that uses one or the other, stick to the standard. For the rest, it's up to you and whether or not you plan on collaborating with others.