Const!
Hello everyone! To get this blog started I’ll be
kicking off with a repository I created recently
that includes some details about const I’ve come
across in my time using C++.
Personally, I am a vigorous user of const in c++.
So much so that I’ve gone a bit over the edge and
started to (or have tried) to use it in places
where it is actually trouble some.
Before I continue I must make a quick aside and stay
that I am not using const for performance. const
might allow the compiler to do some amazing things,
but that’s a good justification. Instead, I use
const for the maintainability and reliability
benefits.
Documented over zealous attempts
This section is my personal wall of shame when it
comes to const. Hopefully with additional context
to explain what I should do instead.
const types with standard containers
Recently I found myself in a situation where I wanted to have a matrix, a.k.a a 2-dimensional array, with a size I knew at code writing time. I wanted that matrix to be populated during runtime by user provided values.
Here’s what that looked like
std::array<std::array<int, 9>, 9> matrix;
Then later on in that program I had wanted to have
const copies of that array. This is what I thought
a constant version was like.
const std::array<const std::array<int, 9>, 9> matrix;
I arrived at this because if I have an array of
std::array<int, 9> that I want to be constant then I
just slap const right in front of it to get
const std::array<int, 9>. From here, I came to the
version above by just repeating this process twice for
the second dimension.
There’s one thing that should’ve served as a warning
that I was going down a path that wasn’t well justified.
This was the fact that I’ve never, in any other project,
seen the templated code that I had to create to make
non-const and const versions easily compatible with
each other. The compiler errors would’ve made some type
of compatibility code necessary. So why didn’t other
projects have it anywhere?
In the case of the project where this question originated,
I continued on ward with creating the compatibility code
for the non-const and const types. I did this as a
learning exercise in template meta programming features
I hadn’t used much, specifically SFINAE and parameter
pack/unpacking.
Now, there was one other more obvious thing that should’ve
made it clear something was wrong. That thing was there
was no const next to the int but if you try the
following:
const std::array<int, 2> array{0,1};
int & pos0 = array[0];
Then you’ll get a compilation error. This compilation error
would’ve lead me to the documentation that I needed to see
to know what was going on. In fact, as I wrote this code
I believed it would not work because part of the benefit of
const on standard containers is that the values returned
from inside them should also be const.
If we check out the documentation for std::array::operator[]
on cppreference then we’ll see that for const std::arrays we’ll access
a const_reference. A const_reference is a type alias
for const value_type & which means that for std::array
a single const is enough to specify all levels are constant.
Thus the simplest solution would’ve been simply:
const std::array<std::array<int, 9>, 9> matrix;
Other const things
Things that I’ve discovered about const that don’t directly relate to some actual code that I’ve tried to work through.
const prevents members from being modified
I’ve known for a long time that using the const
specifier on a member function is what allows that member
function to be called by const version fo the object.
For example:
#include <cstdio>
class Foo
{
private:
int x;
public:
Foo(int x):x(x){}
void bar(void) const
{
std::printf("%d\n", x);
}
};
void FooDoesBar(int x)
{
const Foo foo(x);
foo.bar();
}
But besides allowing the bar method to be called from
const Foo objects, the compiler will also check that we
don’t modify any member variables in the const qualified
method. The following will not compile:
#include <cstdio>
class Foo
{
private:
int x;
public:
Foo(int x):x(x){}
void bar(void) const
{
x = 1;
std::printf("%d\n", x);
}
};
void FooDoesBar(int x)
{
const Foo foo(x);
foo.bar();
}
Rightly so, g++ 8.3.0 tells me:
k.cpp: In member function ‘void Foo::bar() const’:
k.cpp:11:11: error: assignment of member ‘Foo::x’ in read-only object
x = 1;
To me this is a big deal because I frequently tag const on member
variables which causes all kinds of problems. (Can’t use them with
std::vector for example.) Turns out in a large large majority of
cases I don’t actually need the member variables to be const. I
just want them to be treated as const in member functions. Which
is exactly the feature of const qualified member functions i just
described.
Where you can find more
As I learn more and make more const mistakes, I’ll
be updating this repository:
https://github.com/TylerSeanRau/ThingsToRememberAboutConst
with more stories.
Thanks for your time, Tyler Sean Rau
