Constpiracy

I've only watched until Day 16 for now, so I don't know if this has been properly addressed in newer streams.
I was thinking about Casey's words on const-correctness and, with the help of "unions", came up with this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

struct integers {
    union {
        const int x = 0;
        int y;
    };
};

int main()
{
    integers ints;
    ints.y = 1;

    // Prints "integer x = 1 | integer y = 1"
    printf("integer x = %d | integer y = %d", ints.x, ints.y);

    return 0;
}


I don't know if this shows the alleged uselessness of const, but I do think it helps realize it doesn't create a "sacred place" in your computer's memory.
While you are right with this code (unions, meh), try following code. It will create special place in computers memory (by OS) that is read-only:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void f(int* y)
{
    *y = 1;
}

const int x = 123;

int main()
{
    const int* ptr = &x;
    f((int*)ptr);
}


Or this code:
1
2
3
4
5
int main()
{
    char* str = "abcdef";
    str[0] = 'm';
}
Const is just one of those things that really adds very little in practice, yet brings with it incredible downsides.

I used to be very big on "const correctness" but in the end, the viral nature of const and the extreme rigidity it forced upon my code caused me to basically drop its usage. There is of course, your example, among many others where people can just flat out work around const anyways, so it doesn't really buy you anything in the first place other than some false sense of non-modifiability.

It really is very questionable, in my opinion, to be using it. My only use for it really is maybe at a very stable API boundary layer to help make clear that some parameters are to be treated as read-only inputs, but I'm very careful about when I choose to do that even in this case.

Other than that, my remaining use for const is for const string literals.

Edited by Dale Kim on
I use const, but for me it's more about semantics. Related consts I put in an enum, but free floaters, get made const. I prefer that over #define.

It makes it clear that you are not supposed to change the value.
Yep, same with me. I also use const, but not because I think it helps optimizations. Is because it helps to read code. It is basically markup for me to remember that I don't what these structures changed somewhere. And if in some place I found out that I need to change const variable, then that is an indication that I have structured my algorithm or code incorrectly. And it sometimes helps catch a bug (Casey on stream had 2 bugs that const would prevent :)

Anyway, "const" for me is similar how Casey uses "inline" for functions. For modern C/C++ compiler "inline" doesn't mean anything. You can easily put "static" there instead and compiler will do exactly same optimizations (decision to inline or not to inline). It's only about reminding yourself what function should be or not.
Actually, "static" does mean something special for the optimiser. Declaring a function "static" means that (assuming you don't take the address of it) the compiler knows precisely where all of the call sites for that function are, and controls them all in this compilation unit. On modern platforms this gives it the freedom to break the ABI if it wants to.

So, for example, the compiler could decide that it would be more efficient to use a nonstandard calling convention just for that function. Clang definitely does this, and I believe that MSVC does too.

In theory it could mess with exception handling as well but I don't think any compiler does that yet.
Same with inline functions no? You cannot access them outside of translation unit, so compiler can apply same reasoning and optimizations.

EDIT: Ok, you can access them from different translation unit with gcc and clang, but not with MSVC. Great... I'll stick with "static" instead of "inline".

Edited by Mārtiņš Možeiko on
Kind of. Imagine an "inline" function which isn't static, which (for whatever reason) the compiler decides not to inline the function at some call sites. It generates a function and calls it.

The compiler has to assume that this could happen in multiple compilation units. So in general, each compilation unit will have its own copy of this function. The One Definition Rule, however, states that there must be only one copy. This means that the linker has to discard all copies but one.

What this means for the optimiser is that every generated version of that function must be the same. That doesn't mean they have to have identical code, of course; the compilation units could be compiled with different optimisation settings. But they have to do the same thing, and hence they have to satisfy the same ABI.

There is one similarity: If the compiler optimises away all references to an inline function in this compilation unit, then it doesn't need to generate a definition for it in this compilation unit.

Great... I'll stick with "static" instead of "inline".
You should use whatever matches your intention. :)
const should be used everywhere, where it makes sense. Of course when one starts to use const one has to use it in a lot of places (what some people then call "viral") , but this actually is a good thing. It forces you to think about what you're doing leading to better designed code in the end. But this is not an issue if one thinks about this from the beginning. (If adding const to an existing codebase it's also easy if one starts with the leaf functions).

If you don't use the const keyword to mark something as const you have to figure out some other way to document what can be modified and what cannot. If you don't even do that your code basically is not reusable. And since the compiler doesn't care for comments or external documentation he can't complain leading to code that is a major PITA to call. Having this checked by the compiler is much better.

Also using const can help the compiler generate better code. Consider a struct. You assign a value to some members in that struct and then pass a pointer to this struct to some function. After this you read some values from this struct. If the function takes a const pointer the compiler will know that the values in that struct cannot have changed so he doesn't have to read them from memory again. If the pointer was not const the compiler must assume that those are changed and load them again from memory. And of course this can make also other optimizations possible. Of course the compiler can infer this in some cases if the definition of the called function is available, but that's not always the case.

Also this doesn't just concern the compiler. If I have an array of things I want to pass to an function and the parameter is not marked as const (and there is no other documentation) I must assume that this function will change them there and make copies.

Another thing where it is actually required since C++11 are string literals. Try compiling Handmade Hero with clang, and you will see what I mean. It will only compile in C++11 mode, but with lots of warnings.

Also const doesn't just give a false sense of immutability, you can trust this. Yes you can cast the const away and change the values anyways, but this will not always work as intended (see optimizations). Technically this invokes "undefined behavior" int this means that the compiler can do anything it wants. What modern compilers like to do when they face undefined behavior is to just assume the code is unreachable which has before lead to many "interesting" bugs.

TL;DR: const is good for the compiler and good for the programmer as he knows exactly if a function does or does not change the values it receives. This is essential knowledge, so better to make it explicit and checked than to bury it it never up-to-date documentation comments.
Also using const can help the compiler generate better code.
While I'm all pro-const, I've never seen this to be true.

If the function takes a const pointer the compiler will know that the values in that struct cannot have changed so he doesn't have to read them from memory again.
You are confusing const with restrict keyword. Const doesn't guarantee this, because data of structure may alias with memory buffer that is changed by different pointers. Here's an example:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct S { int x; };

int f(const struct S* s, int* x)
{
  int y = s->x; // reading from structure
  *x = 42;
  int z = s->x; // reading again from structure, can not reuse y value

  return y + x;
}

S s = { 5 };
f(&s, &s.x);


This code generates following assembly (gcc -O2):
1
2
3
4
5
6
	movl	4(%esp), %edx
	movl	8(%esp), %ecx
	movl	(%edx), %eax    // reading from structure pointer
	movl	$42, (%ecx)     // putting 42 with to *x
	addl	(%edx), %eax    // reading from structure pointer again (and adding to previous)
	ret


If you stick __restrict keyword (c99) like this:
1
2
3
4
5
6
7
int f( const struct S* __restrict s, int* __restrict x)
{
  int y = s->x;
  *x = 42;
  int z = s->x;
  return y + z;
}

then compiler will read s->x value only once:
1
2
3
4
5
6
	movl	4(%esp), %eax
	movl	8(%esp), %edx
	movl	(%eax), %eax   // reading from structure pointer
	movl	$42, (%edx)    // putting 42 with to *x
	addl	%eax, %eax     // this time reuse value read from structure before
	ret


Of course then it is your fault to modify s->x through *x pointer.

Edited by Mārtiņš Možeiko on
In response to 5sw:

Past me would have agreed with what you said, but today me: I disagree completely.

I'd need to spend a little more time to gather up concrete explanations on why I believe const to be mostly harmful, but I'm fairly sure your claims of compiler optimization are wrong. At best they're misleading or extremely optimistic of compiler capabilities.

Marking parameters as const, especially pointers, guarantees nothing to the compiler about whether or not the values it sees will change. In fact, because compilers must be conservative (aka - correct, not that this is ever achieved), they often ignore const completely precisely because it often means nothing in terms of whether or not a value can change.

More concretely, if I am given a const pointer to anything, yes, I might be able to determine that I will never change the value through that pointer. But it's extremely difficult for me to determine that value at the pointer will never change elsewhere in my program. This is why pointer analysis is incredibly difficult, and why const is basically meaningless in compiler optimizations.

Which is why the restrict keywould exists to let me tell the compiler the pointer is not going to be aliased.

Anyways, this is kind of getting off on a side topic. Needless to say, I very much disagree with the use of const. I might be able to write up something more thorough on my argument (aside from compiler optimizations, which I almost never factor into my code decisions these days) against it later.

Edited by Dale Kim on
@mmozeiko: Yes in your example const doesn't help because it shows aliasing issues. There restrict makes more sense. I was talking more about something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct S { int x; }

void function( const S *s );

S s;
s.x = 123;
function( &s );

int y = s.x;
// With const the compiler can assume that s.x is still 123 and 
// doesn't need to load the value from memory when accessing s.x


I tried this with clang and it does the load again. Don't have any other compilers to test right now. But I checked the C standard and couldn't find anything that said the compiler was required to do that load after the function call.


@DaleKim Yes, of course it's true that a const pointer doesn't guarantee that the object it points to doesn't change. But that is actually not the point. The point is that if a function takes a const pointer argument it promises not to change the value that pointer references. And the compiler will make sure that this doesn't happen by accident.

If you don't use const, how do you deal with functions taking pointers? Do you document somewhere if it mutates values through that pointer? Or do you just not care if something might get changed?

And actually the chances that an object changes while you hold a const pointer to this are rather slim. It could happen in multi-threaded code, but this then is most likely a bug anyways. And if its single-threaded code you have a horrible design error if a value changes inside a function where you only have a const pointer to it.
You cannot make that claim at all. What if function() created a thread that delayed execution of the code that used the pointer to the struct? What's the value of s.x when the code in the thread executes?

The only way to guarantee that s.x = 123 in your example is to pass by value so that the struct is actually copied.

The const keyword is really only good for defining the semantics of how your code should work; it really makes no claim about how your code does work.
owensd
You cannot make that claim at all. What if function() created a thread that delayed execution of the code that used the pointer to the struct? What's the value of s.x when the code in the thread executes?

Well I could guess that theoretically, because "s" is pointer to constant struct thread can never change it's value. Unless you cast const away (but you could cast much worse things).

Anyway this is example is too artificial. How often you write code like this? I don't know why I would write load from structure after I pass it to function as const argument.
Owen is right that const is mostly an annotation for you to expressing intent of 'Do not mess with my data' / 'I do not mess with the data you input into my function'.

IMO it makes a lot of senses to use it, the bigger your team and/or the more physical apart it is. It serves it purpose to a) express your intent and b) protect against people accidentally messing with your data. Unless of course they are really reckless. But then not much helps you anyway.

std::set only returning const references is such an example.