简体   繁体   中英

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.]

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, ie you could pass non-const objects only to functions that do modify them, but not to functions that do not modify them.

PS 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 fx .

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.

A reference to a const object is not the same as a const reference to a non const object, but C++'s type system does not distinguish them.

This is sort of a violation of the LSP; it has the same kind of problem as a reference to a mutable square and rectangle do.

You can create "true const" but you need help at declaration.

template<class T>
struct true_const {
  const T value;
};

a true_const<int>& or true_const<T> const& can be passed around as a reference, and nobody can edit it (without invoking UB) "behind your back".

Of course, a function taking a true const cannot also take a normal object.

void bob( true_const<int>& x ) {
  auto local = x.value;
  call_some_other_function();
  assert(local == x.value); // guaranteed to be true
}

void bob( const int& x ) {
  auto local = x;
  call_some_other_function();
  assert(local == x); // NOT guaranteed to be true
}

const fields in classes are truly const; modifying them is undefined behavior.

A thin wrapper around a type that is const within the class is thus a guarantee the data is const . Then take a reference to that.

Now, the true_const could use some operator support.

template<class T>
struct true_const {
  const T value;
  constexpr T const& get() const { return value; }
  constexpr T const& operator*() const { return get(); }
  constexpr T const* operator->() const { return std::addressof(value); }
  constexpr operator T const&() const { return get(); }

  // concepts-defended operator+,==, etc
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM