48

Disclaimer: I am aware that there are two questions about the usefulness of const-correctness, however, none discussed how const-correctness is necessary in C++ as opposed to other programming languages. Also, I am not satisfied with the answers provided to these questions.

I've used a few programming languages now, and one thing that bugs me in C++ is the notion of const-correctness. There is no such notion in Java, C#, Python, Ruby, Visual Basic, etc., this seems to be very specific to C++.

Before you refer me to the C++ FAQ Lite, I've read it, and it doesn't convince me. Perfectly valid, reliable programs are written in Python all the time, and there is no const keyword or equivalent. In Java and C#, objects can be declared final (or const), but there are no const member functions or const function parameters. If a function doesn't need to modify an object, it can take an interface that only provides read access to the object. That technique can equally be used in C++. On the two real-world C++ systems I've worked on, there was very little use of const anywhere, and everything worked fine. So I'm far from sold on the usefulness of letting const contaminate a codebase.

I am wondering what is it in C++ that makes const necessary, as opposed to other programming languages.

So far, I've seen only one case where const must be used:

#include <iostream>

struct Vector2 {
    int X;
    int Y;
};

void display(/* const */ Vector2& vect) {
    std::cout << vect.X << " " << vect.Y << std::endl;
}

int main() {
    display(Vector2());
}

Compiling this with const commented out is accepted by Visual Studio, but with warning C4239, non-standard extension used. So, if you want the syntactic brevity of passing in temporaries, avoiding copies, and staying standard-compliant, you have to pass by const reference, no way around it. Still, this is more like a quirk than a fundamental reason.

Otherwise, there really is no situation where const has to be used, except when interfacing with other code that uses const. Const seems to me little else than a self-righteous plague that spreads to everything it touches :

The reason that const works in C++ is because you can cast it away. If you couldn't cast it away, then your world would suck. If you declare a method that takes a const Bla, you could pass it a non-const Bla. But if it's the other way around you can't. If you declare a method that takes a non-const Bla, you can't pass it a const Bla. So now you're stuck. So you gradually need a const version of everything that isn't const, and you end up with a shadow world. In C++ you get away with it, because as with anything in C++ it is purely optional whether you want this check or not. You can just whack the constness away if you don't like it.

Anders Hejlsberg (C# architect), CLR Design Choices

Asik
  • 21,506
  • 6
  • 72
  • 131
  • 39
    Hejlsberg isn't entirely correct (In general, don't trust non-C++ programmers to rationalize about C++ design decisions. It always ends up wrong). C++ does not allow you to cast away const-ness freely. If a variable is declared as const, then it *stays* const, and you're not allowed to cast away constness. But if you take a non-const object, which gets implicitly promoted to const, then you're allowed to cast away const-ness. – jalf Sep 02 '09 at 21:13
  • 4
    The new D language features a lot of functional constructs like const in C++. Nowadays OOP is overrated. – GogaRieger Sep 02 '09 at 21:27
  • 1
    I think Anders has a point: You are always allowed to cast away const. You are just not allowed to write to it if the object is really const. In my short life as a c++ programmer i've only had one occasion where i needed to cast const away (`wxStyledTextCtrl` has all its `GetXXX` methods non-const!). If C++ didn't allow me to do that, then i would have been stuck with that crappy API and let it infect my class design. – Johannes Schaub - litb Sep 02 '09 at 21:33
  • 4
    I like D's concept of it. It has "immutable", which says the object is const. And it has "const", to say that merely the access path is const. This is what i don't like about it in C++: If something is const there, you don't know whether it's merely the expression designating the object, or the object itself, and by inheritance then also for the expression (and sadly, you cannot "look through" the expression to find out what of the two is const). – Johannes Schaub - litb Sep 02 '09 at 21:44
  • 4
    I think i heard Jon skeet once rationalize about const, and he said that const for C# would be unrealistic, or something, because it would have to be checked if it wants to be at all useful. But checking for constness on every write access is too expensive. C++ can easily get away with just saying it's undefined behavior in that case, C# can't. – Johannes Schaub - litb Sep 02 '09 at 21:46
  • 1
    There is no requirement to use const. Like all C++ feattures it is tool you can use. – Martin York Sep 02 '09 at 22:39
  • 10
    "German is the only language with a single word meaning 'Schadenfreude'. I speak English, and I get along perfectly fine without using the word 'Schadenfreude'. I think it's pointless, and an expert on the English language says that even in German, you can just say "Vergnügen über das Missgeschick beziehungsweise Unglück eines anderen" instead. Why is he wrong?" ;-) – Steve Jessop Sep 03 '09 at 01:28
  • Another question about that: http://stackoverflow.com/questions/528122/why-doesnt-c-offer-constness-akin-to-c – Johannes Schaub - litb Sep 03 '09 at 18:20
  • hmmm.. I have yet to grasp const in C++ but I used to think its like the `final` keyword in java or `readonly` in c# (though i don't think readonly can be applied to function arguments). Am I wrong? – Mel Jul 09 '13 at 11:09
  • 1
    Since pointers and references allow you to make function parameters be inputs and/or outputs, it is informative to mark input-only params as such with const, i.e. the function signature says, "I promise not to modify this." If the parameter is a class reference, then its const member functions can be called within the function. Consider memcpy() for example. Even without parameter names, you know which param is which. `void * memcpy (void *, const void *, size_t);` – cp.engr Jul 28 '15 at 23:43
  • 1
    It should be mentioned that in "real life" trying to introduce const correctness into any existing code base of considerable size is very probably going to end up in a real nightmare, whereas if designed in from the ground up can be of exceptionally good use improving code quality and eliminating possible pit falls. – tofro Feb 29 '16 at 10:45
  • Using `const` always when you can makes the code more readable and understandable as you immediately notice that ok the following piece of code is not going change this variable. So it's also a form of documentation. – juzzlin Dec 07 '22 at 12:46

14 Answers14

67

Const correctness provides two notable advantages to C++ that I can think of, one of which makes it rather unique.

  • It allows pervasive notions of mutable/immutable data without requiring a bunch of interfaces. Individual methods can be annotated as to whether or not they can be run on const objects, and the compiler enforces this. Yes, it can be a hassle sometimes, but if you use it consistently and don't use const_cast you have compiler-checked safety with regards to mutable vs. immutable data.
  • If an object or data item is const, the compiler is free to place it in read-only memory. This can particularly matter in embedded systems. C++ supports this; few other languages do. This also means that, in the general case, you cannot safely cast const away, although in practice you can do so in most environments.

C++ isn't the only language with const correctness or something like it. OCaml and Standard ML have a similar concept with different terminology — almost all data is immutable (const), and when you want something to be mutable you use a different type (a ref type) to accomplish that. So it's just unique to C++ within its neighboring languages.

Finally, coming the other direction: there have been times I have wanted const in Java. final sometimes doesn't go far enough as far as creating plainly immutable data (especially immutable views of mutable data), and don't want to create interfaces. Look at the Unmodifiable collection support in the Java API and the fact that it only checks at run time whether modification is allowed for an example of why const is useful (or at least the interface structure should be deepend to have List and MutableList) — there is no reason that attempting to mutate an immutable structure can't be a compile-type error.

Michael Ekstrand
  • 28,379
  • 9
  • 61
  • 93
  • Thanks for addressing my point directly, these look like very valid points. +1. – Asik Sep 02 '09 at 21:25
  • 11
    The important point there is that it allows you to express the notion of immutability that cannot be expressed in other languages (even though it is recommended) – David Rodríguez - dribeas Sep 02 '09 at 22:26
  • 2
    "in practice you can [safely cast const away] in most environments". Not exactly. The UB of modifying an object defined as `const` isn't limited to the risk that it might be in ROM. If you write `const int i = 1; mutate((int*)&i); std::cout << i;` then the compiler is allowed to assume that neither `mutate` nor anything else in the program has invoked UB by writing to `i`. In practice you'd expect the modification to be either respected or harmlessly ignored for that code, but in general all sorts of things can go wrong where the optimizer has assumed something that turns out not to be true. – Steve Jessop Mar 14 '14 at 23:49
  • +1 for the second point. (See also C99 Rationale.) This is the only reason that the current specific `const` (as read-only, not `constexpr`) as builtin feature of the language. However, -1 for risking UB in "in practice" - you should not turn off optimization for practice. So, +0. – FrankHB Jul 31 '16 at 14:52
  • 1
    Note in a type system aspect, there can be other kinds of const-correctness based on different equivalence relationships, e.g. key requirements of `std::map`. Some other kinds of optimization like constant propagation or constant folding can still work similarly for these new kinds of "const". Similarly but more specifically, UB could take place again for these const-correctness violations. However, current languages are still too weak to support them (sadly keys of standard associative containers are now always `const`-qualified even if I can guarantee the different equivalence by myself). – FrankHB Jul 31 '16 at 14:57
32

I don't think anybody claims const-correctness is "necessary". But again, classes are not really necessary either, are they? The same goes for namespaces, exceptions,... you get the picture.

Const-correctness helps catch errors at compile time, and that's why it is useful.

Nemanja Trifunovic
  • 24,346
  • 3
  • 50
  • 88
18

Well, it will have taken me 6 years to really understand, but now I can finally answer my own question.

The reason C++ has "const-correctness" and that Java, C#, etc. don't, is that C++ only supports value types, and these other languages only support or at least default to reference types.

Let's see how C#, a language that defaults to reference types, deals with immutability when value types are involved. Let's say you have a mutable value type, and another type that has a readonly field of that type:

struct Vector {
    public int X { get; private set; }
    public int Y { get; private set; }
    public void Add(int x, int y) {
        X += x;
        Y += y;
    }
}

class Foo {
    readonly Vector _v;
    public void Add(int x, int y) => _v.Add(x, y);
    public override string ToString() => $"{_v.X} {_v.Y}";
}

void Main()
{
    var f = new Foo();
    f.Add(3, 4);
    Console.WriteLine(f);
}

What should this code do?

  1. fail to compile
  2. print "3, 4"
  3. print "0, 0"

The answer is #3. C# tries to honor your "readonly" keyword by invoking the method Add on a throw-away copy of the object. That's weird, yes, but what other options does it have? If it invokes the method on the original Vector, the object will change, violating the "readonly"-ness of the field. If it fails to compile, then readonly value type members are pretty useless, because you can't invoke any methods on them, out of fear they might change the object.

If only we could label which methods are safe to call on readonly instances... Wait, that's exactly what const methods are in C++!

C# doesn't bother with const methods because we don't use value types that much in C#; we just avoid mutable value types (and declare them "evil", see 1, 2).

Also, reference types don't suffer from this problem, because when you mark a reference type variable as readonly, what's readonly is the reference, not the object itself. That's very easy for the compiler to enforce, it can mark any assignment as a compilation error except at initialization. If all you use is reference types and all your fields and variables are readonly, you get immutability everywhere at little syntactic cost. F# works entirely like this. Java avoids the issue by just not supporting user-defined value types.

C++ doesn't have the concept of "reference types", only "value types" (in C#-lingo); some of these value types can be pointers or references, but like value types in C#, none of them own their storage. If C++ treated "const" on its types the way C# treats "readonly" on value types, it would be very confusing as the example above demonstrates, nevermind the nasty interaction with copy constructors.

So C++ doesn't create a throw-away copy, because that would create endless pain. It doesn't forbid you to call any methods on members either, because, well, the language wouldn't be very useful then. But it still wants to have some notion of "readonly" or "const-ness".

C++ attempts to find a middle way by making you label which methods are safe to call on const members, and then it trusts you to have been faithful and accurate in your labeling and calls methods on the original objects directly. This is not perfect - it's verbose, and you're allowed to violate const-ness as much as you please - but it's arguably better than all the other options.

Community
  • 1
  • 1
Asik
  • 21,506
  • 6
  • 72
  • 131
  • There's a similar const correctness concept even in JS/TS now (despite being references) - which makes me cry when I write C#. – juzzlin Dec 07 '22 at 12:42
16

const is a way for you to express something. It would be useful in any language, if you thought it was important to express it. They don't have the feature, because the language designers didn't find them useful. If the feature was there, it would be about as useful, I think.

I kind of think of it like throw specifications in Java. If you like them, you would probably like them in other languages. But the designers of the other languages didn't think it was that important.

Lou Franco
  • 87,846
  • 14
  • 132
  • 192
13

You're right, const-correctness isn't necessary. You can certainly write all your code without the const keyword and get things to work, just as you do in Java and Python.

But if you do that, you'll no longer get the compiler's help in checking for const violations. Errors that the compiler would have told you about at compile-time will now be found only at run-time, if at all, and therefore will take you longer to diagnose and fix.

Therefore, trying to subvert or avoid the const-correctness feature is just making things harder for yourself in the long run.

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
8

Programming is writing in a language that will be ultimately processed by the computer, but that is both a way of communicating with the computer and other programmers in the same project. When you use a language, you are restricted to the concepts that can be expressed in it, and const is just one more concept you can use to describe your problem, and your solution.

Constantness enables you to express clearly from the design board to the code one concept that other languages lack. As you come from a language that does not have it, you may seem puzzled by a concept you have never used --if you never used it before, how important can it be?

Language and thought are tightly coupled. You can only express your thoughts in the language you speak, but the language also changes the way you think. The fact that you did not have the const keyword in the languages you worked with implies that you have already found other solutions to the same problems, and those solutions are what seems natural to you.

In the question you argued that you can provide a non mutating interface that can be used by functions that do not need to change the contents of the objects. If you think about it for a second, this same sentence is telling you why const is a concept you want to work with. Having to define a non-mutating interface and implement it in your class is a work around the fact that you cannot express that concept in your language.

Constantness allows you to express those concepts in a language that the compiler (and other programers) can understand. You are establishing a compromise on what you will do with the parameters you receive, the references you store, or defining limits on what the users of your class are allowed to do with the references you provide. Pretty much each non-trivial class can have a state represented by attributes, and in many cases there are invariants that must be kept. The language lets you define functions that offer access to some internal data while at the same time limits the access to a read-only view that guarantees no external code will break your invariants.

This is the concept I miss more when moving to other languages. Consider an scenario where you have a class C that has among others an attribute a of type A that must be visible to external code (users of your class must be able to query for some information on a). If the type of A has any mutating operation, then to keep user code from changing your internal state, you must create a copy of a and return it. The programmer of the class must be aware that a copy must be performed and must perform the (possibly expensive) copy. On the other hand, if you could express constantness in the language, you could just return a constant reference to the object (actually a reference to a constant view of the object) and just return the internal element. This will allow the user code to call any method of the object that is checked as non-mutating, thus preserving your class invariants.

The problem/advantage, all depends on the point of view, of constantness is that it is viral. When you offer a constant reference to an object, only those methods flagged as non-mutating can be called, and you must tell the compiler which of the methods have this property. When you declare a method constant, you are telling the compiler that user code that calls that method will keep the object state. When you define (implement) a method that has a constant signature, the compiler will remind you of your promise and actually require that you do not internally modify the data.

The language enables you to tell the compiler properties of your methods that you cannot express any other way, and at the same time, the compiler will tell you when you are not complying with your design and try to modify the data.

In this context, const_cast<> should never be used, as the results can take you into the realm of undefined behavior (both from a language point of view: the object could be in read-only memory, and from a program point of view: you might be breaking invariants in other classes). But that, of course, you already know if you read the C++FAQ lite.

As a side note, the final keyword in Java has really nothing to do with the const keyword in C++ when you are dealing with references (in C++ references or pointers). The final keyword modifies the local variable to which it refers, whether a basic type or a reference, but is not a modifier of the referred object. That is, you can call mutating methods through a final reference and thus provide changes in the state of the object referred. In C++, references are always constant (you can only bind them to an object/variable during construction) and the const keyword modifies how the user code can deal with the referred object. (In case of pointers, you can use the const keyword both for the datum and the pointer: X const * const declares a constant pointer to a constant X)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Thanks. I do not, as you say, come from a language that doesn't have const; C++ is my most familiar language. I've just looked elsewhere, and wondering why C++ is designed the way it is. I still think defining interfaces is a cleaner, more generic way of expressing any kind of access restrictions than the const keyword, which spreads to everything it touches and has many different meanings depending on context. – Asik Sep 03 '09 at 02:04
  • 4
    I cannot agree with you on the comment. First of all, if you defined interfaces equivalent to const, you would cram up a lot of interfaces (and virtual methods) just to achieve the same result, and the result would be as viral as the const keyword is. The const keyword spreads to everything because of the access restriction it imposes not because of it being a 'magic' keyword. Then I don't really see your last point: '[const] has many different meanings depending on context'. There is just one single meaning for const, or am I missing something? – David Rodríguez - dribeas Sep 03 '09 at 19:44
6

If you are writing programs for embedded devices with data in FLASH or ROM you can't live without const-correctness. It gives you the power to control the correct handling of data in different types of memory.

chrmue
  • 1,552
  • 2
  • 18
  • 35
4

You want to use const in methods as well in order to take advantage of return value optimization. See Scott Meyers More Effective C++ item 20.

count0
  • 2,537
  • 2
  • 22
  • 30
3

This talk and video from Herb Sutter explains the new connotations of const with regards to thread-safety.

Constness might not have been something you had to worry about too much before but with C++11 if you want to write thread-safe code you need to understand the significance of const and mutable

chillitom
  • 24,888
  • 17
  • 83
  • 118
2

In C, Java and C# you can tell by looking at the call site if a passed object can be modified by a function:

  • in Java you know it definitely can be.
  • in C you know it only can be if there is a '&', or equivalent.
  • in c# you need to say 'ref' at the call site too.

In C++ in general you can't tell this, as a non-const reference call looks identical to pass-by-value. Having const references allows you to set up and enforce the C convention.

This can make a fairly major difference in readability of any code that calls functions. Which is probably enough to justify a language feature.

soru
  • 5,464
  • 26
  • 30
2

Anders Hejlsberg (C# architect): ... If you declare a method that takes a non-const Bla, you can't pass it a const Bla. So now you're stuck. So you gradually need a const version of everything that isn't const, and you end up with a shadow world.

So again: if you started to use "const" for some methods you usually forced to use this in most of your code. But the time spent for maintaining (typing, recompiling when some const is missing, etc.) of const-correctness in code seems greater than for fixing of possible (very rare) problems caused by not using of const-correctness at all. Thus lack of const-correctness support in modern languages (like Java, C#, Go, etc.) might result in slightly reduced development time for the same code quality.

An enhancement request ticket for implementing const correctness existed in the Java Community Process since 1999, but was closed in 2005 due to above mentioned "const pollution" and also compatibility reasons: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070

Although C# language has no const correctness construct but similar functionality possibly will appear soon in "Microsoft Code Contracts" (library + static analysis tools) for .NET Framework by using [Pure] and [Immutable] attributes: Pure functions in C#

Community
  • 1
  • 1
Vlad Rudenko
  • 2,363
  • 1
  • 24
  • 24
  • "If you declare a method that takes a non-const Bla, you can't pass it a const Bla. So now you're stuck." How so? You have an object that you don't want changed. Why do you want to pass it to a method that has as part of it's signature the possibility that it will change anything passed to it? Really, do you expect to be able to declare `const int FOO = 42;` and then do `FOO++;`? `const` is simply a guarantee that code won't change a value, and it enables the compiler to help you ensure that is so. In my experience, const correctness reduces overall development time. – Rob K Jul 06 '15 at 18:40
2

Actually, it's not... not entirely, anyway.

In other languages, especially functional or hybrid languages, like Haskell, D, Rust, and Scala, you have the concept of mutability: variables can be mutable, or immutable, and are usually immutable by default.

This lets you (and your compiler/interpreter) reason better about functions: if you know that a function only takes immutable arguments, then you know that function isn't the one that's mutating your variable and causing a bug.

C and C++ do something similar using const, except that it's a much less firm guarantee: the immutability isn't enforced; a function further down the call stack could cast away the constness, and mutate your data, but that would be a deliberate violation of the API contract. So the intention or best practice is for it to work quite like immutability in other languages.

All that said, C++ 11 now has an actual mutable keyword, alongside the more limited const keyword.

  • `mutable` came long before C++11, and I'm not sure it is relevant to what you're thinking. Also, enforcement seems an odd thing to quibble over, since pretty much nothing in C++ is enforced that strictly; e.g. if I declare `int x;`, there is nothing that enforces the idea that `x` is storing an `int` object, since I can cast that away too. –  Jul 06 '15 at 06:31
1

The const keyword in C++ (as applied to parameters and type declarations) is an attempt to keep programmers from shooting off their big toe and taking out their whole leg in the process.

The basic idea is to label something as "cannot be modified". A const type can't be modified (by default). A const pointer can't point to a new location in memory. Simple, right?

Well, that's where const correctness comes in. Here are some of the possible combinations you can find yourself in when you use const:

A const variable Implies that the data labeled by the variable name cannot be modified.

A pointer to a const variable Implies that the pointer can be modified, but the data itself cannot.

A const pointer to a variable Implies that the pointer cannot be modified (to point to a new memory location), but that the data to which the pointer points can be modified.

A const pointer to a const variable Implies that neither the pointer nor the data to which it points can be modified.

Do you see how some things can be goofy there? That's why when you use const, it's important to be correct in which const you are labeling.

The point is that this is just a compile-time hack. The labeling just tells the compiler how to interpret instructions. If you cast away from const, you can do whatever you want. But you'll still have to call methods that have const requirements with types that are cast appropriately.

Randolpho
  • 55,384
  • 17
  • 145
  • 179
0

For example you have a funcion:

void const_print(const char* str)
{
    cout << str << endl;
}

Another method

void print(char* str)
{
    cout << str << endl;
}

In main:

int main(int argc, char **argv)
{
    const_print("Hello");
    print("Hello");        // syntax error
}

This because "hello" is a const char pointer, the (C-style) string is put in read only memory. But it's useful overall when the programmer knows that the value will not be changed.So to get a compiler error instead of a segmentation fault. Like in non-wanted assignments:

const int a;
int b;
if(a=b) {;} //for mistake

Since the left operand is a const int.

Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • An unfortunate aside: for backwards compatibility reasons, there's a (deprecated) loophole that a string literal can be passed into a `char*`, so your syntax error actually isn't. –  Jul 06 '15 at 06:37