Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

On the weak semantics of references-to-const (and pointers-to-const)

This has probably been already asked.

Why is it allowed to assign a reference-to-const to a non-const variable?

Why is this allowed

int mut {0};
const int & r_to_c {mut};
mut = 1;
// now r_to_c changed to 1!
// But it was supposed to be a reference to something constant!

?

Sure, I cannot mutate the value from the reference-to-const itself. I cannot

r_to_c = 2;

but isn't the const qualification enforcing too little? I would expect, from a promise of const-ness, that binding to mutable variables was disallowed.

Otherwise what guarantees is const giving me? They seem pretty weak, and it seems that this could easily trick programmers to shoot themselves in their foot.

I know that C++ has a reputation for allowing people to shoot themselves in their foot. I don't have a problem with allowing dangerous things. In this case, my problem is that in this case it seems that it is purposefully deceiving, given that the semantics of const here is not the one would expect it.

Mine is a question about the compiler and the language semantics, not about references in particular (I could have asked the same question using a pointer-to-const that is assigned to the address of a non-const variable. Like int mut{0}; const int * p_to_c{&mut};).

Why is the semantics of a reference-to-const (or pointer-to-const) just "you can't use this particular window to modify the thing you see (but if you have other windows that are non-const, you can modify it)" instead of a more powerful "this can only be a window to something that was declared constant and that the compiler guarantees it stays constant"?


[Note on terminology: I use the expression "reference-to-const" instead of "const reference" because a "const reference", interpreted as T& const - consistently with calling T* const a "const pointer" -, does not exist.]

like image 962
Michele Piccolini Avatar asked Dec 31 '22 17:12

Michele Piccolini


2 Answers

but isn't the const qualification enforcing too little? I would expect, from a promise of const-ness, that binding to mutable variables was disallowed.

No it is not "too little". You are expecting the wrong thing.

First, whether you bind a const reference does not make the object itself const. That would be strange:

void foo(int& x) {
    static const int& y = x;
}

When I call foo:

int x = 42;
foo(x);

I cannot know whether somebody else will keep a const reference to my non-const x.

Otherwise what guarantees is const giving me?

You cannot modify something via a const reference:

void bar(const int& x);
int x = 0;
bar(x);

When I call a function that takes a const& then I know that it will not modify my (non-const) parameter. If const references would not bind to non-const objects then there would be no way to make this last example work, i.e. you could pass non-const objects only to functions that do modify them, but not to functions that do not modify them.

P.S. I can understand your confusion. It is sometimes overlooked that holding a constant reference does not imply that the object cannot be modified. Consider this example:

#include <cstddef>
#include <iostream>

struct foo {
    const int& x;
};

int main() {
    int y = 0;
    foo f{x};
    std::cout << f.x;   // prints 0
    y = 42;
    std::cout << f.x;   // prints 42
}

Printing the value of the member to the screen yields two different results, even though foo::x is a constant reference! It is a "constant reference" not a "reference to a constant". What const actually means here: You cannot modify y through f.x.

like image 179
463035818_is_not_a_number Avatar answered Jan 14 '23 00:01

463035818_is_not_a_number


The ability to bind a const-reference to a mutable variable is actually a very valuable feature to have in the language. Consider that we might want to have a mutable variable.

int mut {0};
// ... some time later
mut = 1;

This is perfectly reasonable; it's a variable that is going to change during the execution of the program.

Now let's say we want to print the value of this variable, and would like to write a function to do that.

void print(int param)  // or 'int &' to avoid a copy, 
                       // but the point here is that it's non-const 
{
  std::cout << param;
}

This is fine, but clearly the function is not changing the parameter. We would like that to be enforced so that mistakes like param = 42; are caught by the compiler. To do that, we would make param a const & parameter.

void print(int const & param);

It would be quite unfortunate if we couldn't call this function with arguments that are non-const. After all, we don't care that the parameter might be modified outside the function. We just want to say that the parameter is guaranteed not to be modified by print, and binding a const & to a mutable variable serves exactly that purpose.

like image 33
cigien Avatar answered Jan 13 '23 22:01

cigien