Is it allowed to cast away const on a const-defined object as long as it is not actually modified?
Asked Answered
T

1

23

Is the following allowed:

const int const_array[] = { 42 };

int maybe_inc(bool write, int* array) {
  if (write) array[0]++;
  return array[0];
}

int main() {
  return maybe_inc(false, const_cast<int *>(const_array));
}

In particular, is it OK to cast-away the constness of const_array, which was defined as const, as long as the object is not actually modified, as in the example?

Thomas answered 3/2, 2019 at 15:11 Comment(2)
Indeed, you are safe as long as no write is actually performed. The const_cast itself does not result in UB. See also (possible duplicate?): #29883827Karlakarlan
Possible duplicate of Is it safe to remove const via const_cast and invoke a non-const function that does not modify the resulting object?Nagual
T
29

Yes. This is entirely legal. (It is dangerous, but it is legal.) If you (attempt to) modify an object declared const, then the behaviour is undefined.

From n4659 (which is the last draft of C++17), section 10.1.7.1 [dcl.type.cv] para 4:

Except that any class member declared mutable (10.1.1) can be modified, any attempt to modify a const object during its lifetime (6.8) results in undefined behavior

My emphasis. That is from C++17, but this has been true of all versions of C++.

If you look at the section on const_cast there is a note that

[ Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a const_cast that casts away a const-qualifier76 may produce undefined behavior (10.1.7.1). — end note ]

Notes are not normative, but this strongly implies that obtaining a non-const reference or pointer to a const object is legal. It is the write that is not allowed.


New references for C++20:

From N4680 (which is the last draft of C++20), section 9.2.8.1 [dcl.type.cv] para 4:

Any attempt to modify (7.6.19, 7.6.1.5, 7.6.2.2) a const object (6.8.3) during its lifetime (6.7.3) results in undefined behavior.

If you look at the section on const_cast, section 7.6.1.10 [expr.const.cast] para 6 linking to above statement:

[Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data member resulting from a const_cast that casts away a const-qualifier may produce undefined behavior (9.2.8.1).]

Tabulate answered 3/2, 2019 at 15:16 Comment(12)
Interesting, so this answer is wrong (or at least imprecise) when it says "You are not allowed to const_cast variables that are actually const." - the actual restriction is that you are not allowed to modify variable that are actually const (and of course casting away const removes the guardrails that would normally prevent that)?Thomas
@Thomas Yup.Tabulate
@Thomas Confirming, that quote is incorrect and your interpretation is correct.Overexcite
It's actually amazing how many wrong highly upvoted answers one can find on the subject. Here's another. https://mcmap.net/q/23057/-is-const_cast-safeVirgule
Why is this legal? Is there a limitation in the way C++ is implemented that makes it impossible to forbid this at compile time without losing important features?Disney
@Disney It's to allow passing "actually const" objects to old APIs that were written before const existed in C, and haven't been updated, and for calling functions like maybe_inc in the original question where the programmer can see the array won't be modified, but the compiler can't. C and C++ have a long tradition of "trust the programmer".Tabulate
@MartinBonnersupportsMonica: Possibly "before const existed" but more likely "by teams who didn't realize how valuable const-correctness actually is"Carafe
In C++23, it's currently (N4928) [dcl.type.cv] 9.2.9.2 (4) and [expr.const.cast] 7.6.1.11 (6) with the same wording.Commune
@BenVoigt: There are many scenarios where a function may accept a pointer and pass it back to client code which may or may not modify storage identified thereby. Such functions may be correctly used either by clients that modify the storage and expect that it will be modified, or by clients that do not modify the storage and rely upon its not being modified. Const correctness would require having separate versions of the function for the two use cases, but duplicating code for such purposes would be uglier than using a const-cast to accommodate both.Timberland
@supercat: Or the library could forward callback data of type uintptr_t and completely avoid getting entangled with "does the client pass a pointer to const or to non-const".Carafe
@BenVoigt: Consider a collection with a function to invoke a caller-supplied callback on every item in the collection. If the callback modifies things in the collection, things in the collection will be modified. If it doesn't, they won't. I guess the collection could convert pointers to the objects to type uintptr_t before invoking the callback, but that would seem pretty horrible compared with having it accept a const-qualified pointer to the collection, but accept a callback which takes a non-const-qualified pointer to the element type.Timberland
@supercat: Generic (templated) collections are (a) completely different from "a function may accept a pointer and pass it back to client code and (b) going to need both const-iterator and non-const-iterator versions anyway, to respect const-ness of the collection (besides which, you can't use const_cast to get between const and non-const iterator flavors)Carafe

© 2022 - 2024 — McMap. All rights reserved.