Preserving Const

Preserving Const

18 Jun 2018    

Make everything const by default (not necessarily constexpr). This includes everything from functions, to local variables (most important) to function parameters. const greatly helps with compiler optimisations and leads to a lot of code being resolved at compile time and reducing aliasing & duplication. Using const is also beneficial in conveying a contract to the user, greatly increasing readibility in the process. When you encounter a const function parameter or variable, you can rest assured that it will not be modified which helps since you need not track it’s state as you navigate the code.

When writing new local variables always start off with making them const and only make them mutable when you actually have to modify them in a couple of places or if it inhibits move semantics. For example:

auto const currentFont = HasFont(font) ? UseCurrentFont : FontFallback;

// Cannot be const (just yet)
/* const */ size_t fontSize = currentFont->Size();

if (currentFont->Is2x()) {
    fontSize /= 2;
}

Although constexpr is better, it is not necessary in most cases, especially when using literals (numbers, strings), const is good enough to allow for compiler optimizations. Use constexpr if that variable is not in a function scope i.e. either a global or class scoped variable. And of course, for non-literals and non-fundamental types, constexpr requires some additional checks.

// Both are equally optimal
auto const MaxRowsAllowed = 10;
constexpr auto MaxRowsAllowed = 10;

class Foo {
static constexpr auto MAX_NUM = 10; // Static NOT required in C++17

// Please don't do this, const is redundant here
static constexpr const auto MAX_NUM = 10;

// Use constexpr with non-literals if required
constexpr auto DefaultColor{MakeRed()};

int getClassId() {
  int const generatedId = 5;
  return generatedId;
}
};

In cases like the first code example, we can actually still preserve the constness of the fontSize variable by using an idiom known as Immediately Invoked Lambda Expression (IILE) or basically what other languages call IIFE. This involves definition and calling a lambda in the same line.

size_t const fontSize = [&] {
    if (currentFont->Is2x()) {
        fontSize /= 2;
    }    
    
    return currentFont->Size();
}(); // Immediately called here

This allows you to preserve const correctness thus reducing bugs and improving optimisation in the process!