const_cast VS mutable ? any difference? - c++

From my understanding , mutable cancels the constness of a variable
Class A {
void foo() const {
m_a = 5;
}
mutable int m_a;
};
But also const_cast :
void print (char * str)
{
cout << str << endl;
}
int main () {
const char * c = "this is a line";
print ( const_cast<char *> (c) );
return 0;
}
So , what changes one from the other ?
Thanks

const_cast cannot cancel constness of an object. const_cast can only remove constness from an access path to an object. Access path is a pointer or a reference to an object. Removing the constness from the access path has absolutely no effect on the object itself. Even if you use const_cast to remove the constness of the access path, that still does not necessarily give you the permission to modify the object. Whether you can do it or not still depends on the object itself. If it is const, you are not allowed to modify it and any attempts to do so will result in undefined behavior.
For example, this illustrates the intended use of const_cast
int i = 5; // non-constant object
const int *p = &i; // `p` is a const access path to `i`
// Since we know that `i` is not a const, we can remove constness...
int *q = const_cast<int *>(p);
// ... and legally modify `i`
*q = 10;
// Now `i` is 10
The only reason the above is legal and valid is the fact that i is actually a non-constant object, and we know about it.
If the original object was really constant, then the above code would produce undefined behavior:
const int j = 5; // constant object
const int *p = &j; // `p` is a const access path to `j`
int *q = const_cast<int *>(p); // `q` is a non-const access path to `j`
*q = 10; // UNDEFINED BEHAVIOR !!!
C++ language does not allow you to modify constant objects and const_cast is completely powerless here, regardless of how you use it.
mutable is a completely different thing. mutable creates a data filed that can be legally modified even if the containing object is declared const. In that sense mutable does allow you to modify [some designated parts of] constant objects. const_cast, on the other hand, can't do anything like that.

The difference is that const_cast can't cheat, but mutable is an exception to the rules.
On the first snippet m_a is mutable, and thus an exception to the rule that you can't modify data members on const member functions.
On the second snippet, const_cast tries to cheat, but really can't: while the type has changed, actual modification is not allowed: the string is truly const. Attempting to modify it would cause the program to exhibit undefined behaviour.

The difference is semantic - i. e. same generated code, same run-time results (constness is a purely compile-time construct anyway), but the two constructs convey a slightly different meaning.
The idea is that you use mutable for variables that are in the class, but don't constitute the state of the object. The classic example is the current position in a blob object. Navigating in the blob does not count as "modifying" the blob in a way that matters. By using mutable, you're saying "this variable may change, but the object is still the same". You're stating that for this particular class, const-ness does not mean "all variables are frozen".
const_cast, on the other way, means that you're violating existing const correctness and hope to get away with it. Probably because you're working with a 3rd party API that does not respect const (e. g. an old school C-based one).

Simply put, declaring a member variable as mutable makes it write-accessible from any constant method of that class without any other special syntax. const_cast on the other hand has to be performed whenever you want write access to an otherwise constant variable, and that variable doesn't even have to be a class member.
Unless you want to explicitly allow write access to a member variable, using const_cast in every case of violation of const correctness is preferred, if only to clearly state your intentions.
On a side note, const_cast can also be used to add or remove the volatile modifier.

Related

Dereferencing a pointer to constant

Let's say we have a class called object.
int main(){
object a;
const object* b = &a;
(*b);
}
Question:
b is a pointer to const, but the object it is pointing to is actually not a constant object. My question is, when we dereference the pointer "b" why do we get a constant object instead of what it is actually pointing at which is normal non constant object.
Because that's how the built-in dereference operator * works in C++. If you dereference a pointer of type T *, you get an lvalue of type T. In your case T is const object.
Neither * operator, not the pointer itself cares (or knows) that the object the pointer is pointing to is actually non-constant. It just can't be any other way within the concept of static typing used by C++ language.
The whole purpose of const-qualification at the first (and deeper) levels of indirection is to provide you with an ability to create restrictive access paths to objects. I.e. by creating a pointer-to-const you are deliberately and willingly preventing yourself (or someone else) from modifying the pointee, even if the pointee is not constant.
The type of an expression is just based on the declared types of the variables in the expression, it can't depend on dynamic run-time data. For instance, you could write:
object a1;
const object a2;
const object *b;
if (rand() % 2 == 0) {
b = &a1;
} else {
b = &a2;
}
(*b);
The type of *b is determined at compile-time, it can't depend on what rand() returns when you run the program.
Since b is declared to point to const object, that's what the type of *b is.
Because the const keyword means 'you can not modify that object' rather than 'the object can not be modified'.
This is useful when you pass an object to some function to use the object's value only, but but not to modify it:
// We expect PrintMyString to read the string
// and use its contents but not to modify it
void PrintMyString(const char *string);
void myfunction(int number)
{
// build and print a string of stars with given length
if(number>0 && number<=16]
{
char string[17];
int i;
for(i=0, i<number; i++)
string[i] = '*';
string[number] = '\0';
PrintMyString(string);
}
}
The function PrintMyString will get an array of characters, but the array will be passed as 'read only' to the function. The array is certainly modifiable within the owning function, but the PrintMyString can only read what it gets and not alter the contents.
There are plenty of other answers here, but I'm driven to post this because I feel that many of them provide too much detail, wander off topic, or assume knowledge of C++ jargon that the OP almost certainly doesn't have. I think that's unhelpful to the OP and others at a similar phase of their careers so I'm going to try to cut through some of that.
The situation itself is actually very straightforward. The declaration:
const SomeClassOrType *p = ...
simply says that whatever p is pointing to cannot be modified through that pointer, and that is useful to ensure that code that gains access to that object via p doesn't modify it when it isn't supposed to. It is most often used in parameter declarations so that functions and methods know whether or not they can modify the object being passed in (*).
It says nothing at all about the const-ness of what is actually being pointed to. The pointer itself, being just a simple soul, does not carry that information around with it. It only knows that, so far as the pointer is concerned, the object pointed to can be read from but not written to.
Concrete example (stolen from CiaPan):
void PrintString (const char *string);
char string [] = "abcde";
const char *p = string;
PrintString (p);
void PrintString (const char *ptr_to_string)
{
ptr_to_string [0] = 0; // oops! but the compiler will catch this, even though the original string itself is writeable
}
You can just pass string to PrintString directly of course, same difference, because the parameter is also declared const.
Another way to look at this is that const is information provided by the programmer to help the compiler check the correctness / consistency of the code at compile time. It lets the compiler catch mistakes like the one above, and perhaps perform better optimisations. By the time you actually run your code, it is history.
(*) The modern idiom is probably to use a const reference, but I don't want to muddy the waters by throwing that into the main discussion.
const object* b = &a;
This means: b is a pointer to a const (read-only) object
Doesn't say anything about the const-ness of a. It just means b has no right to modify a.
Also known as low-level const.
Contrary to the above, top-level const is when the pointer itself is const.
object *const b = &a; // b is a const pointer to an object
b = &some_other_object; // error - can't assign b to another object
// since b is a const pointer
*b = some_value; // this is fine since object is non-const
Simply put,
For plain poiters, the result of unary operator * is a reference to pointed-to type.
So, if the type pointed-to is const-qualified, the result is a reference to a constant.
References to constants can bind to any objects, even to mutable objects of otherwise the same type. When you bind a constref to a value, you promise to not ad-hoc modify that value through this refence (you still can do it explicitly though, cast it to a non-const reference).
Likewise, const-pointers can point to objects still modifiable otherwise.
By writing
const object* b = &a;
you declare that b is a pointer (*) to a const of type object, to which you then assign the address of a. a is of type object (but not const); you are permitted to use the adress of the non-const object in place of an adress of an const object.
When you dereference * b however, the compiler can only go according to your declaration - thus *b is a const object (however you can still modify a as you like, so beware of thinking that the object b points to cannot change - it mere cannot be changed via b)
const object* b = &a;
b will treat what it points to as const, i.e. it cannot change a
object* const b = &a;
b itself is const, i.e. it cannot point to other object address, but it can change a
Because at run-time it might be a const object, meaning that any operations performed on the dereference must be compatible with const. Ignore the fact that in your example it has been pointed to a non-const object and cannot point to anything else, the language doesn't work like that.
Here’s a specific example of why it is the way it is. Let’s say you declare:
int a[] = {1, 2, 3};
constexpr size_t n_a = sizeof(a)/sizeof(a[0]);
extern int sum( const int* sequence, size_t n );
sum_a = sum( a, n_a );
Now you implement sum() in another module. It doesn’t have any idea how the original object you’re pointing to was declared. It would be possible to write a compiler that tagged pointers with that information, but no compilers in actual use today do, for a number of good reasons.¹
Inside sum(), which might be in a shared library that cannot be recompiled with whole-program optimization, all you see is a pointer to memory that cannot be altered. In fact, on some implementations, trying to write through a pointer to const might crash the program with a memory-protection error. And the ability to reinterpret a block of memory as some other type is important to C/C++. For example, memset() or memcpy() reinterprets it as an array of arbitrary bytes. So the implementation of sum() has no way to tell the provenance of its pointer argument. As far as it’s concerned, it’s just a pointer to const int[].
More importantly, the contract of the function says that it’s not going to modify the object through that pointer.² It could simply cast away the const qualifier explicitly, but that would be a logic error. If you’re declaring a pointer const, it’s like engaging the safety on your gun: you want the compiler to stop you from shooting yourself in the foot.
¹ Including extra instructions to extract the address from a pointer, extra memory to store them, compatibility with the standard calling convention for the architecture, breaking a lot of existing code that assumes things like long being able to hold a pointer, and the lack of any benefit.
² With the pedantic exception of mutable data members.

Applying const_cast on this-> pointer

I was playing with some code to remove constant-ness of variable.
int *i = new int(202);
int *j = new int(402);
int *const iptr = i; // Constant pointer
//iptr = j ; // Not allowed. Constant pointer cannot point to another location.
(*iptr)++; // Allowed
const_cast<int *>(iptr) = j;
cout<< *iptr // prints 402
It works as expected but when I try to remove constantness of "this" pointer, Compiler doesnt allow it, i.e. it shows squiggling lines underneath the const_cast statement.
class A
{
public:
A(A * obj)
{
const_cast<A *>(this) = obj;
}
};
When I hovered mouse (I am using VS2014) over "this" and "iptr" from earlier code, I could see the type is same i.e. <classname> *const
Can anybody please explain what is going on under the hood?
Cheers,
Saket
I am afraid you did not understand what const_cast is for.
In C++, const is used in two instances:
a const object logical value cannot be changed (and, barring mutable fields, its bitwise value cannot be changed either)
const* or const& are read-only pointers or references
const_cast is not about changing const objects, it's about modifying non-const objects through read-only pointers or references
Bear in mind though that shooting yourself in the foot is easy, because when you get a const& how do you know whether the original object is const or not ? You do not. And if you attempt to change one that is const, a unicorn appears, or maybe the devil (also know as Undefined Behavior: anything may happen).
Now, the correlation with this is tricky. Strictly speaking this is an r-value (meaning it can only appear as-is on the R ight-hand side of =), though it is often described as simply being const for simplicity's sake, so in a constructor of A this is described as having type A* const. Thankfully for us, even in this approximation const_cast is a bad idea since the original object is const.
Therefore, the recommendation is:
Junior: Do not use const_cast, reinterpret_cast or C-style casts (because it is not obvious when they desugar to one of the first two).
Senior: You are not experienced enough.
Expert: Come on! How can you claim you are an expert and attempt to use them ?
this is not an l-value.
You cannot assign it to point to something else.
You can do *this = *obj; which does not require a const_cast
You can do a const_cast<A*>(this) to override constness, and as with any other const_cast is fraught with danger, but it would enable you to perform a const/non-const overload without having to duplicate the implementation e.g.
T& A::get()
{
// some complex code to find the right reference, assigning to t
return t;
}
const T& A::get() const
{
// implement in terms of above function
return (const_cast<A*>(this))->get(); // invokes above
// and automatically converts the reference to const
}

Lambda: Why are captured-by-value values const, but capture-by-reference values not?

Why are captured-by-value values const, but captured-by-reference objects not:
int a;
auto compile_error = [=]()
{
a = 1;
}
auto compiles_ok = [&]()
{
a = 1;
}
To me this seem illogical but it seem to be the standard? Especially as the unwanted modification of a captured value may be an annoying bug, but chances are high that the consequences are limited to lambda scope, whereas unwanted modification of objects captured by reference will often lead to more serious effects.
So why not capture by const reference per default? Or at least support [const &] and [&]? What are the reasons for this design?
As workaround you are probably supposed to use std::cref wrapped const references captured by value?
Let's say you are capturing a pointer by value. The pointer itself is const, but access to the object it points to is not.
int i = 0;
int* p = &i;
auto l = [=]{ ++*p; };
l();
std::cout << i << std::endl; // outputs 1
This lambda is equivalent to:
struct lambda {
int* p;
lambda(int* p_) : p(p_) {}
void operator()() const { ++*p; }
};
The const on the operator()() makes usage of p equivalent to declaring it as:
int* const p;
Similar thing happens with a reference. The reference itself is "const" (in quotes because references cannot be reseated), but access to the object it refers to is not.
Captured references are also const. Or rather, references are always implicitly const -- there is no syntax in the language that allows you to change where a reference points to. a = 1; when a is a reference is not changing the reference, but changing the thing that the reference references.
When you talk about "const reference", I think you are confused. You are talking about "reference to const int" (const int &). The "const" there refers to the thing the reference points to, not the reference itself. It's analogous with pointers: with "pointer to const int" (const int *), the pointer itself is not const -- you can assign to a variable of this type all you want. A real "const pointer" would be int *const. Here, you cannot assign to something of this type; but you can modify the int it points to. Hence, the "const" for the pointer or reference is separate from the "const" for the thing it points to. You can also have a "const pointer to const int": const int *const.
My logic says the following: lambdas are just a piece of code, with optional needed references. In the case when you need to actually copy something (which usually happens for memory management purposes, such as copying a shared_ptr), you still don't really want the lambda to have its own state. That's quite an unusual situation.
I think only the 2 following options feel "right"
Enclose some code using some local variables, so that you can "pass it around"
The same as above, only add memory management, because maybe you want to execute that piece of code asynchronously or something, and the creating scope will disappear.
But when a lambda is "mutable", i.e., it captures values which aren't const, this means that it actually supports its own state. Meaning, every time you call a lambda, it could yield a different result, which isn't based on its actual closure, but again, on its internal state, which is kind of counter-intuitive in my book, considering that lambdas originate in functional languages.
However, C++, being C++, gives you a way to bypass that limitation, by also making it a bit uglier, just to make sure you're aware of the fact you're doing something strange.
I hope this reasons with you.

C++: Why is const_cast evil?

I keep hearing this statement, while I can't really find the reason why const_cast is evil.
In the following example:
template <typename T>
void OscillatorToFieldTransformer<T>::setOscillator(const SysOscillatorBase<T> &src)
{
oscillatorSrc = const_cast<SysOscillatorBase<T>*>(&src);
}
I'm using a reference, and by using const, I'm protecting my reference from being changed. On the other hand, if I don't use const_cast, the code won't compile. Why would const_cast be bad here?
The same applies to the following example:
template <typename T>
void SysSystemBase<T>::addOscillator(const SysOscillatorBase<T> &src)
{
bool alreadyThere = 0;
for(unsigned long i = 0; i < oscillators.size(); i++)
{
if(&src == oscillators[i])
{
alreadyThere = 1;
break;
}
}
if(!alreadyThere)
{
oscillators.push_back(const_cast<SysOscillatorBase<T>*>(&src));
}
}
Please provide me some examples, in which I can see how it's a bad idea/unprofessional to use a const_cast.
Thank you for any efforts :)
Because you're thwarting the purpose of const, which is to keep you from modifying the argument. So if you cast away the constness of something, it's pointless and bloating your code, and it lets you break promises that you made to the user of the function that you won't modify the argument.
In addition, using const_cast can cause undefined behaviour. Consider this code:
SysOscillatorBase<int> src;
const SysOscillatorBase<int> src2;
...
aFieldTransformer.setOscillator(src);
aFieldTransformer.setOscillator(src2);
In the first call, all is well. You can cast away the constness of an object that is not really const and modify it fine. However, in the second call, in setOscillator you are casting away the constness of a truly const object. If you ever happen to modify that object in there anywhere, you are causing undefined behaviour by modifying an object that really is const. Since you can't tell whether an object marked const is really const where it was declared, you should just never use const_cast unless you are sure you'll never ever mutate the object ever. And if you won't, what's the point?
In your example code, you're storing a non-const pointer to an object that might be const, which indicates you intend to mutate the object (else why not just store a pointer to const?). That might cause undefined behaviour.
Also, doing it that way lets people pass a temporary to your function:
blah.setOscillator(SysOscillatorBase<int>()); // compiles
And then you're storing a pointer to a temporary which will be invalid when the function returns1. You don't have this problem if you take a non-const reference.
On the other hand, if I don't use const_cast, the code won't compile.
Then change your code, don't add a cast to make it work. The compiler is not compiling it for a reason. Now that you know the reasons, you can make your vector hold pointers to const instead of casting a square hole into a round one to fit your peg.
So, all around, it would be better to just have your method accept a non-const reference instead, and using const_cast is almost never a good idea.
1 Actually when the expression in which the function was called ends.
by using const, I'm protecting my reference from being changed
References can't be changed, once initialized they always refer to the same object. A reference being const means the object it refers to cannot be changed. But const_cast undoes that assertion and allows the object to be changed after all.
On the other hand, if I don't use const_cast, the code won't compile.
This isn't a justification for anything. C++ refuses to compile code that may allow a const object to be changed because that is the meaning of const. Such a program would be incorrect. const_cast is a means of compiling incorrect programs — that is the problem.
For example, in your program, it looks like you have an object
std::vector< SysOscillatorBase<T> * > oscillators
Consider this:
Oscillator o; // Create this object and obtain ownership
addOscillator( o ); // cannot modify o because argument is const
// ... other code ...
oscillators.back()->setFrequency( 3000 ); // woops, changed someone else's osc.
Passing an object by const reference means not only that the called function can't change it, but that the function can't pass it to someone else who can change it. const_cast violates that.
The strength of C++ is that it provides tools to guarantee things about ownership and value semantics. When you disable those tools to make the program compile, it enables bugs. No good programmer finds that acceptable.
As a solution to this particular problem, it looks likely that the vector (or whatever container you're using) should store the objects by value, not pointer. Then addOscillator can accept a const reference and yet the stored objects are modifiable. Furthermore, the container then owns the objects and ensures they are safely deleted, with no work on your part.
The use of const_cast for any reason other than adapting to (old) libraries where the interfaces have non-const pointers/references but the implementations don't modify the arguments is wrong and dangerous.
The reason that it is wrong is because when your interface takes a reference or pointer to a constant object you are promising not to change the object. Other code might depend on you not modifying the object. Consider for example, a type that holds an expensive to copy member, and that together with that it holds some other invariants.
Consider a vector<double> and a precomputed average value, the *average is updated whenever a new element is added through the class interface as it is cheap to update then, and if it is requested often there is no need to recompute it from the data every time. Because the vector is expensive to copy, but read access might be needed the type could offer a cheap accessor that returns a std::vector<double> const & for user code to check values already in the container. Now, if user code casts away the const-ness of the reference and updates the vector, the invariant that the class holds the average is broken and the behavior of your program becomes incorrect.
It is also dangerous because you have no guarantee that the object that you are passed is actually modifiable or not. Consider a simple function that takes a C null terminated string and converts that to uppercase, simple enough:
void upper_case( char * p ) {
while (*p) {
*p = ::to_upper(*p);
++p;
}
}
Now lets assume that you decide to change the interface to take a const char*, and the implementation to remove the const. User code that worked with the older version will also work with the new version, but some code that would be flagged as an error in the old version will not be detected at compile time now. Consider that someone decided to do something as stupid as upper_case( typeid(int).name() ). Now the problem is that the result of typeid is not just a constant reference to a modifiable object, but rather a reference to a constant object. The compiler is free to store the type_info object in a read-only segment and the loader to load it in a read-only page of memory. Attempting to change it will crash your program.
Note that in both cases, you cannot know from the context of the const_cast whether extra invariants are maintained (case 1) or even if the object is actually constant (case 2).
On the opposite end, the reason for const_cast to exist was adapting to old C code that did not support the const keyword. For some time functions like strlen would take a char*, even though it is known and documented that the function will not modify the object. In that case it is safe to use const_cast to adapt the type, not to change the const-ness. Note that C has support for const for a very long time already, and const_cast has lesser proper uses.
The const_cast would be bad because it allows you to break the contract specified by the method, i.e. "I shall not modify src". The caller expects the method to stick to that.
It's at least problematic. You have to distinguish two constnesses:
constness of the instantiated variable
This may result in physical constness, the data being placed in a read-only segment
constness of the reference parameter / pointer
This is a logical constness, only enforced by the compiler
You are allowed to cast away the const only if it's not physically const, and you can't determine that from the parameter.
In addition, it's a "smell" that some parts of your code are const-correct, and others aren't. This is sometimes unavoidable.
In your first example, you assign a const reference to what I assume is a non-const pointer. This would allow you to modify the original object, which requires at least a const cast. To illustrate:
SysOscillatorBase<int> a;
const SysOscillatorBase<int> b;
obj.setOscillator(a); // ok, a --> const a --> casting away the const
obj.setOscilaltor(b); // NOT OK: casting away the const-ness of a const instance
Same applies to your second example.
, while I can't really find the reason why const_cast is evil.
It is not, when used responsibily and when you know what you're doing. (Or do you seriously copy-paste code for all those methods that differ only by their const modifier?)
However, the problem with const_cast is that it can trigger undefined behavior if you use it on variable that originally was const. I.e. if you declare const variable, then const_cast it and attempt to modify it. And undefined behavior is not a good thing.
Your example contains precisely this situation: possibly const variable converted into non-const. To avoid the problem store either const SysOscillatorBase<T>*(const pointer) or SysOscillatorBase<T> (copy) in your object list, or pass reference instead of const reference.
You are violating a coding contract. Marking a value as const is saying you can use this value but never change it. const_cast breaks this promise and can create unexpected behaviour .
In the examples you give, it seems your code is not quite right. oscillatorSrc should probably be a const pointer, although if you really do need to change the value then you should not pass it in as a const at all.
Basicly const promises you and the compiler that you will not change the value. The only time you should use when you use a C library function (where const didn't exist), that is known not to change the value.
bool compareThatShouldntChangeValue(const int* a, const int* b){
int * c = const_cast<int*>(a);
*c = 7;
return a == b;
}
int main(){
if(compareThatShouldntChangeValue(1, 7)){
doSomething();
}
}
You probably need to define you container as containing const objects
template <typename T> struct Foo {
typedef std::vector<SysOscillator<T> const *> ossilator_vector;
}
Foo::ossilator_vector<int> oscillators;
// This will compile
SysOscillator<int> const * x = new SysOscillator<int>();
oscillators.push_back(x);
// This will not
SysOscillator<int> * x = new SysOscillator<int>();
oscillators.push_back(x);
That being said if you have no control over the typedef for the container maybe it
is ok to const_cast at the interface between your code and the library.

const casting an int in a class vs outside a class

I read on the wikipedia page for Null_pointer that Bjarne Stroustrup suggested defining NULL as
const int NULL = 0;
if "you feel you must define NULL." I instantly thought, hey.. wait a minute, what about const_cast?
After some experimenting, I found that
int main() {
const int MyNull = 0;
const int* ToNull = &MyNull;
int* myptr = const_cast<int*>(ToNull);
*myptr = 5;
printf("MyNull is %d\n", MyNull);
return 0;
}
would print "MyNull is 0", but if I make the const int belong to a class:
class test {
public:
test() : p(0) { }
const int p;
};
int main() {
test t;
const int* pptr = &(t.p);
int* myptr = const_cast<int*>(pptr);
*myptr = 5;
printf("t.p is %d\n", t.p);
return 0;
}
then it prints "t.p is 5"!
Why is there a difference between the two? Why is "*myptr = 5;" silently failing in my first example, and what action is it performing, if any?
First of all, you're invoking undefined behavior in both cases by trying to modify a constant variable.
In the first case the compiler sees that MyNull is declared as a constant and replaces all references to it within main() with a 0.
In the second case, since p is within a class the compiler is unable to determine that it can just replace all classInstance.p with 0, so you see the result of the modification.
Firstly, what happens in the first case is that the compiler most likely translates your
printf("MyNull is %d\n", MyNull);
into the immediate
printf("MyNull is %d\n", 0);
because it knows that const objects never change in a valid program. Your attempts to change a const object leads to undefined behavior, which is exactly what you observe. So, ignoring the undefined behavior for a second, from the practical point of view it is quite possible that your *myptr = 5 successfully modified your Null. It is just that your program doesn't really care what you have in your Null now. It knows that Null is zero and will always be zero and acts accordingly.
Secondly, in order to define NULL per recommendation you were referring to, you have to define it specifically as an Integral Constant Expression (ICE). Your first variant is indeed an ICE. You second variant is not. Class member access is not allowed in ICE, meaning that your second variant is significantly different from the first. The second variant does not produce a viable definition for NULL, and you will not be able to initialize pointers with your test::p even though it is declared as const int and set to zero
SomeType *ptr1 = Null; // OK
test t;
SomeType *ptr2 = t.p; // ERROR: cannot use an `int` value to initialize a pointer
As for the different output in the second case... undefined behavior is undefined behavior. It is unpredictable. From the practical point of view, your second context is more complicated, so the compiler was unable to prefrom the above optimization. i.e. you are indeed succeeded in breaking through the language-level restrictions and modifying a const-qualified variable. Language specification does not make it easy (or possible) for the compilers to optimize out const members of the class, so at the physical level that p is just another member of the class that resides in memory, in each object of that class. Your hack simply modifies that memory. It doesn't make it legal though. The behavior si still undefined.
This all, of course, is a rather pointless exercise. It looks like it all began from the "what about const_cast" question. So, what about it? const_cast has never been intended to be used for that purpose. You are not allowed to modify const objects. With const_cast, or without const_cast - doesn't matter.
Your code is modifying a variable declared constant so anything can happen. Discussing why a certain thing happens instead of another one is completely pointless unless you are discussing about unportable compiler internals issues... from a C++ point of view that code simply doesn't have any sense.
About const_cast one important thing to understand is that const cast is not for messing about variables declared constant but about references and pointers declared constant.
In C++ a const int * is often understood to be a "pointer to a constant integer" while this description is completely wrong. For the compiler it's instead something quite different: a "pointer that cannot be used for writing to an integer object".
This may apparently seem a minor difference but indeed is a huge one because
The "constness" is a property of the pointer, not of the pointed-to object.
Nothing is said about the fact that the pointed to object is constant or not.
The word "constant" has nothing to do with the meaning (this is why I think that using const it was a bad naming choice). const int * is not talking about constness of anything but only about "read only" or "read/write".
const_cast allows you to convert between pointers and references that can be used for writing and pointer or references that cannot because they are "read only". The pointed to object is never part of this process and the standard simply says that it's legal to take a const pointer and using it for writing after "casting away" const-ness but only if the pointed to object has not been declared constant.
Constness of a pointer and a reference never affects the machine code that will be generated by a compiler (another common misconception is that a compiler can produce better code if const references and pointers are used, but this is total bogus... for the optimizer a const reference and a const pointer are just a reference and a pointer).
Constness of pointers and references has been introduced to help programmers, not optmizers (btw I think that this alleged help for programmers is also quite questionable, but that's another story).
const_cast is a weapon that helps programmers fighting with broken const-ness declarations of pointers and references (e.g. in libraries) and with the broken very concept of constness of references and pointers (before mutable for example casting away constness was the only reasonable solution in many real life programs).
Misunderstanding of what is a const reference is also at the base of a very common C++ antipattern (used even in the standard library) that says that passing a const reference is a smart way to pass a value. See this answer for more details.