Check for const correctness with the C++ Core Guidelines Checker

This blog post was written by Sunny Chatterjee and Andrew Pardoe

The C++ Core Guidelines focus on simple ways that you can improve the correctness and safety of your code. We introduced the C++ Core Guidelines Checkers to help automate enforcement of the C++ Core Guidelines in your code.

One of the easiest and most important changes you can make to your code is to mark immutable data as `const`. It’s not us and the Core Guidelines who believe that: see this fantastic blog post from Bruce Dawson about the benefits of adding const to your code. (He also mentions that on MSVC removing const can make some code faster, but that reflects a compiler bug that we are fixing thanks to Bruce’s feedback.) Because const is so important we’ve added a new C++ Core Guidelines checker about const correctness.

We created four new rules in the C++ Core Guidelines checker that cover all of the rules in the currently contained in the Constants and Immutability section of the C++ Core Guidelines. We didn’t actually add these checks for all of the rules—we implemented a check for rule #2, “By default, make member functions const” but removed it because it raised too many false positives on valid code—see below for details. Also, the tool will not warn that you could mark a stub function as const because we recognize that you’ll probably just remove the const later when you implement the function.

Const checker rules

Con.1: By default, make objects immutable

This rule is a general idea that states that we should always mark objects as const unless we’re writing to them. We cover this rule through more specific implementation of subsequent rules in our checker.

Con.3: By default, pass pointers and references to consts

Our checker enforces this rule. You can pass a pointer or reference to a non-const object, but if you do so, the caller shall assume their argument will be modified. If the function does not modify the object, we should mark the object as const to make the intent explicit.

Advantages of this check
  • Makes the intention of the callee explicit to the caller about whether or not an argument will be modified.
  • Future modifications in function body doesn’t change the expectations of the caller.

We use certain heuristics to reduce noise –

  • We don’t ask to mark arguments that are passed by value or the pointer arguments themselves as const.
  • We don’t ask unused arguments be marked as const since we don’t have enough information about their intent.
  • We don’t enforce this on virtual functions as the author may want to follow a specific derived behavior.
Examples
// Expect 26461: The input pointer argument b in function f7 can be marked as const
int f7(const int *a, int *b)
{
    return *a + *b;
}

struct S0
{
    virtual void m();
};

// Expect 26461 on 'p' but don't report on unnamed parameter.
S0 f8(int *p, int *)
{
    (p == nullptr);

    // Don't report on return UDT.
    return{};
}

Con.4: Use const to define objects with values that do not change after construction

Our checker enforces this rule. It’s similar to con.3, but applies to all variables and not just pointer or reference arguments. It helps prevent surprise from unexpected changes in object values.

Advantages of this check are pretty similar to con.3
  • Makes it easier to reason about code if we know if an object is immutable at the point of declaration.
  • Future modification of the code cannot change the immutable property of the object.

Like con.3, we use certain heuristics to reduce noise –

  • We avoid suggesting const usage on un-used variables – they rarely add any value.
  • We don’t suggest to mark the pointer or reference themselves as const, since users mostly care about the data that they point to.
Examples
int f5()
{
    // Expect 26496: Variable m is assigned only once, use const.
    int m = 5;
    const int a = 10;
    if (m > a)
        return m;
    return a;
}

Con.5: Use constexpr for values that can be computed at compile time and F.4: If a function may have to be evaluated at compile time, declare it constexpr

Our checker encourages programmers to declare functions that may have to be evaluated at compile time as constexpr.

Examples
// Expect 26497: could be marked constexpr if compile-time evaluation is desired
int f1(int a, int b)
{
    return a + b;
}

constexpr int f2(int a, int b)
{
    return a + b;
}

void f3()
{
   // Compile-time evaluation
    constexpr int m = f2(10, 20);
    
    // Expect 26498: This function call f2 can use constexpr if compile-time evaluation is desired.
    const int m2 = f2(10, 20);
}

Rule 2: the one we didn’t include

Con.2: By default, make member functions const

In the initial prototype, we included this check. However, after running this check on some real-world code, we decided to remove it from the shipping version of the checker. We didn’t want programmers to mark their member functions as const when they were logically non-const. For the intrepid, there’s a good discussion of logical and physical constness on the isocpp.org web page: https://isocpp.org/wiki/faq/const-correctness#logical-vs-physical-state.

Here’s an example where the member function is logically non-const. You could mark member function bar as const, e.g., void bar() const { *data_  = GetData(); }. While it doesn’t alter the pointer data_ itself, it could alter the memory pointed to by data_. Thus this function is not logically const.

class Test
{
public:
    // This method should be marked “const” since it doesn’t change the logical state of the object.
    MyData foo() const { return *data_; } 
    
    // This method shouldn’t be blindly marked as “const”. It doesn’t alter the pointer data_ itself.
    // However, it alters the state of memory pointed-to by it.
    void bar() const { *data_ = GetData(); }

private:
    // data_ is a pointer to a “MyData” object
    MyData *data_;
};

 

Send us your feedback!

As always, we welcome your feedback. For problems, let us know via the Report a Problem option, either from the installer or the Visual Studio IDE itself.For suggestions, let us know through UserVoice. And you can always reach us through e-mail at cppcorecheck@microsoft.com.