How to Use 'Const_Cast' to Modify a Constant Variable

How to change the constness of a variable in C++?

How to change the constness of a variable in C++?

You should never change the constness of an inherently const qualified variable!

An const qualified variable is declared with the aim that it should not be modified after it is initialized. By attempting to change the constness you break the contract with the compiler.

If you need to modify a variable, simply do not declare it as const.

Note that C++ provides const_cast to remove or add constness to a variable. But, while removing constness it should be used to remove constness off a reference/pointer to something that was not originally constant.


What is happening?

What is happening is Undefined Behavior.

You should not be changing the constness of an const qualified variable, If you do so through some pointer hackery what you get is Undefined behavior.

In case of undefined behavior the standard does not mandate compilers to provide a compile time diagnostic.Note though that some compilers may warn you if you compile with highest warning level.

Also, undefined behavior means that when you run the program anything can happen and the compiler might show you any result and it does not warrant an explanation.


Why the result?

In this case since i is constant probably the compiler inlines the const variable as a part of its optimization and hence the pointer hackery does not affect the value of i since it already inlined by compiler it simply puts the inlined value wherever i is encountered in the code.

Note that the compiler does so because you made an contract with the compiler,

Heres my i and it will never be changed throughout the lifetime of my program.

The compiler is free to apply whatever optimizations it can want as long as it adheres to the contract and it does so in this case.

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)
{
// ...
}

Is it possible to change value of a constant variable via reinterpret_cast?

The thing that is interesting for me, how the program output is 5, while debugger clearly indicates the value of a is 10?

This depends entirely on the compiler. It could output 10, 5, crash, ... because it is undefined behavior.

If you want to know why the output of the binary created by a particular compiler has a certain result for undefined behavior, you have to look at the generated output of the compiler. This can be done using e.g. godbolt.org

For your code the output gcc (11.2) generates is:

        push    rbp
mov rbp, rsp
sub rsp, 16
mov BYTE PTR [rbp-9], 5
lea rax, [rbp-9]
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov BYTE PTR [rax], 10
mov esi, 5
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
mov eax, 0
leave
ret

Here you can see that the compiler correctly assumes that the value of a will not change. And replaces std::cout << unsigned(a) << std::endl; with std::cout << unsigned(5) << std::endl;:

        mov     esi, 5
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)

If you remove the const from a the output is:

        movzx   eax, BYTE PTR [rbp-9]
movzx eax, al
mov esi, eax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)

const_cast doesn't change the value

Trying to modify a constant value leads to undefined behavior, just don't do it.

As for why it doesn't change, the compiler sees that it is a compile-time constant and may store it in a read-only segment.


For the second program, the variable i can't be stored in a read-only segment, it's stored on the stack like any other local variable. However, since you marked i as constant attempting to modify it is still undefined behavior.

The reason the main program prints the old value, is because you pass the variable by value meaning it gets copied into the local variable i in the ChangeValue function. If the variable wasn't a constant in the main or the ChangeValue function, the value of i in the main function would still not change.

If you change the ChangeValue function to take its argument by reference, you might get the same behavior as the first program.

C/C++ changing the value of a const

you need to cast away the constness:

linux ~ $ cat constTest.c
#include <stdio.h>

void modA( int *x )
{
*x = 7;
}

int main( void )
{

const int a = 3; // I promisse i won't change a
int *ptr;
ptr = (int*)( &a );

printf( "A=%d\n", a );
*ptr = 5; // I'm a liar, a is now 5
printf( "A=%d\n", a );

*((int*)(&a)) = 6;
printf( "A=%d\n", a );

modA( (int*)( &a ));
printf( "A=%d\n", a );

return 0;
}
linux ~ $ gcc constTest.c -o constTest
linux ~ $ ./constTest
A=3
A=5
A=6
A=7
linux ~ $ g++ constTest.c -o constTest
linux ~ $ ./constTest
A=3
A=3
A=3
A=3

also the common answer doesn't work in g++ 4.1.2

linux ~ $ cat constTest2.cpp
#include <iostream>
using namespace std;
int main( void )
{
const int a = 3; // I promisse i won't change a
int *ptr;
ptr = const_cast<int*>( &a );

cout << "A=" << a << endl;
*ptr = 5; // I'm a liar, a is now 5
cout << "A=" << a << endl;

return 0;
}
linux ~ $ g++ constTest2.cpp -o constTest2
linux ~ $ ./constTest2
A=3
A=3
linux ~ $

btw.. this is never recommended... I found that g++ doesn't allow this to happen.. so that may be the issue you are experiencing.

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.

const_cast a const member in a class constructor

It is Undefined Behavior. Per Paragraph 7.1.6.1/4 of the C++11 Standard:

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

In this case, it seems like you want your object to "become" constant after construction. This is not possible.

If your vector is meant to be const, you shall initialize it in the constructor's initialization list:

qqq(vector<foo>& other) 
: my_foo(std::move(other))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
}

Notice, that unless you have a good reason for passing by pointer - in which case, you should also be checking whether the pointer is non-null - you should consider passing by reference (as shown above), which is the common practice.

UPDATE:

As Pete Becker correctly points out in the comments, proper design would suggest that the decision to move from the vector argument should belong to the caller of qqq's constructor, and not to the constructor itself.

If the constructor is always supposed to move from its argument, then you could let it accept an rvalue reference, making it clear what the constructor itself is expecting out of the caller:

qqq(vector<foo>&& other) 
// ^^
: my_foo(std::move(other))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
}

This way, the caller would have to provide an rvalue in input to qqq's constructor:

std::vector<foo> v;
// ...
qqq q1(v); // ERROR!
qqq q2(std::move(v)); // OK! Now the client is aware that v must be moved from


Related Topics



Leave a reply



Submit