Are there any downsides to marking all variables you don't modify const?

C++RefactoringConstants

C++ Problem Overview


After much googling I've found a lot about marking functions and their parameters as const, but no guide on marking variables as const.

Here's a really simple example:

#include <string>
#include <iostream>

void example(const std::string& x) {
  size_t length = x.length();
  for (size_t i = 0; i < length; ++i) {
    std::cout << x.at(i) << std::endl;
  }
}

int main() {
  example("hello");
}

Why not make

size_t length = x.length();

const like

const size_t length = x.length();

by convention?

I know such a small, simple example really doesn't show any huge benefit to this, but it seems like it'd be helpful in a larger codebase where you might accidentally mutate a variable you shouldn't have mutated.

Despite that benefit, I don't really see it used that much (in the C++ codebases I've seen) or mentioned nearly as much as making functions and their parameters const.

Is there some downside to doing this other than having to type 5 extra characters? I haven't found much on the topic, and I don't want to shoot myself in the foot if it's a problem having so many consts.

C++ Solutions


Solution 1 - C++

There are no downsides to marking variables you don't modify const.

There are some up-sides though: the compiler will help you diagnose when you unintentionally modify a variable you shouldn't/didn't mean to and the compiler may (although due to the language having const_cast and mutable this is rare) generate better code.

So, I'd advise; use const where you can. There are no downsides and your compiler can potentially help you spot bugs. No reason not to (except for a bit of extra typing).

Note that this extends to member functions as well. Make them const when you can - it lets them be used in more contexts and helps users reason about the code ("calling this function won't modify the object" is valuable information).

Solution 2 - C++

I can think of at least two downsides:

  • verbosity: more words, more symbols to process, ...
  • inertia: if you need to modify it, you'll have to go and remove this const

and both are worth it.


Verbosity is an often heard argument against explicitness, however people often mistake reading speed with understanding speed. There is a balance to be found between verbosity and explicitness, certainly, too verbose may drown out useful information but too implicit/terse may not present information that has to be reconstructed/inferred/deduced/.

Personally, I use a strongly typed statically checked language so that the compiler picks out my mistake as early as possible; annotating with const is both giving information to the reader and the compiler. I judge it worth the extra 6 symbols.

As for inertia, removing const is probably only a small cost of the change... and it repays itself by forcing you to go through all the places where it's used and review the surrounding code to ensure it's actually alright to remove this const. Suddenly modifying a particular piece of data in a code path where it previously was immutable requires ensuring that no part of the code path (or its callers) accidentally relied on this immutability.

Solution 3 - C++

Instead of this ¹non-standard code:

#import <string>
#import <iostream>

void example(const std::string& x) {
  size_t length = x.length();
  for (size_t i = 0; i < length; ++i) {
    std::cout << x.at(i) << std::endl;
  }
}

int main() {
  example("hello");
}

… I'd write this:

#include <string>
#include <iostream>
using namespace std;

void example( string const& s )
{
    for( char const ch : s )
    {
        cout << ch << '\n';
    }
}

auto main()
    -> int
{ example( "hello" ); }

The main place I could add const, relative to the original code, was for the ch variable in the loop. I think that's nice. const is generally desirable because it reduces the possible code actions one has to consider, and range based loops let you have more const.

The main drawback of using const for most things, is when you have to relate to C APIs.

Then one just has to make some gut feeling decisions about whether to copy data, or trust in the documentation and use a const_cast.


        Addendum 1:
Do note that const on a return type prevents move semantics. As far as I know this was first noted by Andrei Alexandrescu in his Mojo (C++03 move semantics) article in Dr Dobbs Journal:

> [A] const temporary looks like an oxymoron, a contradiction in terms. Seen from a practical perspective, const temporaries force copying at destination.

So, this is one place where one should not use const.

Sorry that I forgot to mention this originally; I was reminded by user bogdan's comment on another answer.


        Addendum 2:
In the same vein (to support move semantics), if the last thing done with a formal argument is to store a copy somewhere, then instead of passing by reference to const it can be better to use a non-const argument passed by value, because it can be simply moved from.

I.e., instead of

string stored_value;

void foo( string const& s )
{
    some_action( s );
    stored_value = s;
}

… or the redundancy of optimized

string stored_value;

void foo( string const& s )
{
    some_action( s );
    stored_value = s;
}

void foo( string&& s )
{
    some_action( s );
    stored_value = move( s );
}

… consider just writing

string stored_value;

void foo( string s )
{
    some_action( s );
    stored_value = move( s );
}

It can be slightly less efficient for the case of lvalue actual argument, it discards the advantages of const (constraints on what the code could possibly do), and it breaks a uniform convention of using const wherever possible, but it doesn't perform badly in any situation (which is the main goal, to avoid that) and it's smaller and possibly more clear code.


Notes:
¹ Standard C++ does not have an #import directive. Also, those headers, when properly included, are not guaranteed to define size_t in the global namespace.

Solution 4 - C++

For the local variable size_t length in a short method like this it doesn't really matter. The downside of extra verbosity basically balances with the relative safety of avoiding typos accidentally modifying the length. Do whatever your local style guide, or your own gut feeling tells you.

For a longer or more complex method, it might be different. But then again, if you have so complex a method that it matters, maybe you should at least consider refactoring your code to simpler pieces... Anyway, if you read and understand the code, extra hint provided by explicit const is kinda irrelevant - nice but irrelevant.


Slightly related, though you didn't ask about it: For the reference parameter of your example method, you definitely do want const, because you might need to pass it a const string. Only if you want to disable passing const string (because you think you'll add code to modify it), you should omit const there.

Solution 5 - C++

> I know such a small, simple example really doesn't show any huge > benefit to this, but it seems like it'd be helpful in a larger > codebase where you might accidentally mutate a variable you shouldn't > have mutated.

The problem is that this basically never actually happens.

On the other hand, const is a disease that will spread through your codebase like a plague. As soon as you declare a const variable, all the things you need on it must be const, and so they must only call const functions, and it just never stops.

const isn't remotely worth the price you pay for it in the infinite majority of situations. There's only a couple of cases where const actually protects you (e.g. set keys), but even then, it's debatable if you'd have to be a total moron to try that in the first place, and probably not worth all the language rules and incessant code duplication and redundant metalogic.

const is a nice idea that might be nice in theory, but the practical realities are that const is a total waste of time and space. Burn it with fire.

Solution 6 - C++

I agree with most of the answers given so far, on the other hand, some aspects are still missing.

When defining interfaces, the const keyword is your friend. But you should know that it's also somewhat limited and sometimes even egoistic - this is what I'd call its downsides.

Let's have another close look on the question:

> Are there any downsides to marking all variables you don't modify const?`

If you observe that you don't modify something, you may say that it's effectively a constant. Also code analysis tools can detect this, even your compiler will already know it. But this observation should not trigger an add-const reflex in you.

Instead think about the variable itself, ask

  • Is it useful at all?
  • Is it also intended to be constant?

Sometimes an intermediate variable can simply be removed, sometimes a small rework (adding or removing a function) can be done to improve the code.

Adding the const keyword may harden your code against errors elsewhere, but also against changes at the declaration point.

Let me also add a word about member variables that don't change. If you decide to declare a member variable const, you have to initialize it in the initializer list of the constructor of the containing class, this extends the preconditions for constructing objects of this class.

So don't add const wherever your compiler allows for it. Do not be seduced to "petrify your code" ;-)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionm0meniView Question on Stackoverflow
Solution 1 - C++Jesper JuhlView Answer on Stackoverflow
Solution 2 - C++Matthieu M.View Answer on Stackoverflow
Solution 3 - C++Cheers and hth. - AlfView Answer on Stackoverflow
Solution 4 - C++hydeView Answer on Stackoverflow
Solution 5 - C++PuppyView Answer on Stackoverflow
Solution 6 - C++WolfView Answer on Stackoverflow