Behavior of Const_Cast in C++

behavior of const_cast in C++

As-written the way you're doing this is undefined behavior. If you wanted to see the effects of const_cast<> in a defined manner:

int a = 5;                  // note: not const. regular object.
const int& cref = a; // const-reference to same object.
cref = 7; // illegal. cref is a const reference.
const_cast<int&>(cref) = 7; // legal. the original object a is not const.

The only reason this is defined behavior is due to the non-const nature of the original variable, a. You cannot take an outright-const object and simply cast away the const-ness, which is what your posted code did. (at least as it has been explained to me on several occasions).

behaviour of const_cast

it helps to cast away constness of an expression of type Type

No, Type is the type of the result, not the type of the operand.

What i think is const of this pointer should be casted away

this has type const ConstTest*. const_cast<ConstTest*>(this) has type ConstTest*. That's what "casting away const" from a pointer-to-const means.

I feel code should have been ConstTest *c =
const_cast<ConstTest>(*this)

The result of const_cast<T> has type T, that's how it's defined. Maybe you would have defined it differently, but tough luck, you don't get a ConstTest* by writing const_cast<ConstTest>, you get it by writing const_cast<ConstTest*>. Your preferred syntax is not available.

You can either do ConstTest &c = const_cast<ConstTest&>(*this) or ConstTest *c = const_cast<ConstTest*>(this), so pick your favorite.

The result of a const_cast expression is an rvalue unless Type is a
reference type. In this case, the result is an lvalue.

why so and why it is not true in case of pointers?

It is true of pointers. ConstTest* is not a reference type, and the result of const_cast<ConstTest*>(this) is an rvalue. You then assign that value to the variable c.

Is this undefined behavior with const_cast?

Quote from cppreference:

Even though const_cast may remove constness or volatility from any pointer or reference, using the resulting pointer or reference to write to an object that was declared const or to access an object that was declared volatile invokes undefined behavior.

So yes, modifying constant variables is undefined behavior. The output you see is caused by the fact that you tell the compiler that the value of a will never change, so it can just put a literal 0 instead of the variable a in the cout line.

Undefined behaviour with const_cast

I was hoping that someone could clarify exactly what is meant by undefined behaviour in C++.

Technically, "Undefined Behaviour" means that the language defines no semantics for doing such a thing.

In practice, this usually means "don't do it; it can break when your compiler performs optimisations, or for other reasons".

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined.

In this specific example, attempting to modify any non-mutable object may "appear to work", or it may overwrite memory that doesn't belong to the program or that belongs to [part of] some other object, because the non-mutable object might have been optimised away at compile-time, or it may exist in some read-only data segment in memory.

The factors that may lead to these things happening are simply too complex to list. Consider the case of dereferencing an uninitialised pointer (also UB): the "object" you're then working with will have some arbitrary memory address that depends on whatever value happened to be in memory at the pointer's location; that "value" is potentially dependent on previous program invocations, previous work in the same program, storage of user-provided input etc. It's simply not feasible to try to rationalise the possible outcomes of invoking Undefined Behaviour so, again, we usually don't bother and instead just say "don't do it".

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined.

As a further complication, compilers are not required to diagnose (emit warnings/errors) for Undefined Behaviour, because code that invokes Undefined Behaviour is not the same as code that is ill-formed (i.e. explicitly illegal). In many cases, it's not tractible for the compiler to even detect UB, so this is an area where it is the programmer's responsibility to write the code properly.

The type system — including the existence and semantics of the const keyword — presents basic protection against writing code that will break; a C++ programmer should always remain aware that subverting this system — e.g. by hacking away constness — is done at your own risk, and is generally A Bad Idea.™

I can imagine a case where lack of awareness that C-style cast can result in a const_cast being made could occur without being noticed.

Absolutely. With warning levels set high enough, a sane compiler may choose to warn you about this, but it doesn't have to and it may not. In general, this is a good reason why C-style casts are frowned upon, but they are still supported for backwards compatibility with C. It's just one of those unfortunate things.

What are Legitimate uses of const_cast

You are right, uses of const_cast often indicates a design flaw, or an API that is out of your control.

However, there is an exception, it's useful in the context of overloaded functions. I'm quoting an example from the book C++ Primer:

// return a reference to the shorter of two strings
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}

This function takes and returns references to const string. We can call the function on a pair of non-const string arguments, but we’ll get a reference to a const string as the result. We might want to have a version of shorterString that, when given non-const arguments, would yield a plain reference. We can write this version of our function using a const_cast:

string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string&>(s1),
const_cast<const string&>(s2));
return const_cast<string&>(r);
}

This version calls the const version of shorterString by casting its arguments to references to const. That function returns a reference to a const string, which we
know is bound to one of our original, non-const arguments. Therefore, we know it is safe to cast that string back to a plain string& in the return.

How to use const_cast?

You are not allowed to const_cast variables that are actually const. This results in undefined behavior. const_cast is used to remove the const-ness from references and pointers that ultimately refer to something that is not const.

So, this is allowed:

int i = 0;
const int& ref = i;
const int* ptr = &i;

const_cast<int&>(ref) = 3;
*const_cast<int*>(ptr) = 3;

It's allowed because i, the object being assigned to, is not const. The below is not allowed:

const int i = 0;
const int& ref = i;
const int* ptr = &i;

const_cast<int&>(ref) = 3;
*const_cast<int*>(ptr) = 3;

because here i is const and you are modifying it by assigning it a new value. The code will compile, but its behavior is undefined (which can mean anything from "it works just fine" to "the program will crash".)

You should initialize constant data members in the constructor's initializers instead of assigning them in the body of constructors:

Student(const Student & s) 
: Person(p.getName(), p.getEmailAddress(), p.getBirthDate()),
school(0),
studentNumber(s.studentNumber)
{
// ...
}

const_cast and UB

a) Firstly, the use of 'may'. Why is
it 'may'? Other places in the Standard
are very definitive about the
undefined behavior

Don't look too deeply into the use of the word may here. The point is, casting away constness in this case causes undefined behavior.

The C++ standard uses "may" or "might" often, as in:

1.3.12: Undefined behavior may also be expected when this International
Standard omits the description of any
explicit definition of behavior.

Emphasis mine. Basically, the standard uses the word "may" as in "is allowed to".

b) Why is that the casting away the
constness of a originally const object
not straight away 'undefined
behavior'. Why is it that a write is
required for UB to be triggered?

A write triggers UB because it's possible that const objects can be stored in read-only memory on certain platforms.

Is const_cast safe?

const_cast is safe only if you're casting a variable that was originally non-const. For example, if you have a function that takes a parameter of a const char *, and you pass in a modifiable char *, it's safe to const_cast that parameter back to a char * and modify it. However, if the original variable was in fact const, then using const_cast will result in undefined behavior.

void func(const char *param, size_t sz, bool modify)
{
if(modify)
strncpy(const_cast<char *>(param), sz, "new string");
printf("param: %s\n", param);
}

...

char buffer[16];
const char *unmodifiable = "string constant";
func(buffer, sizeof(buffer), true); // OK
func(unmodifiable, strlen(unmodifiable), false); // OK
func(unmodifiable, strlen(unmodifiable), true); // UNDEFINED BEHAVIOR


Related Topics



Leave a reply



Submit