Const_cast as undefined behavior?

N

Nephi Immortal

Please check to make sure if my code is safe since I chose to define only one class instead of two classes as const class and non-const class. Are you positive sure if undefined behavior does not occur? I am told that if I declare const_cast before undefined behavior will occur.

Is copy constructor in class Y safe?


class Y;

class X
{
friend class Y;

public:
X( int data1, int data2 );
Y Get() const;
Y Set();

private:
int data1;
int data2;
};

class Y
{
public:
Y( X& p );
Y( const Y& r );

int Value1() const;
void Value1( int data );

int Value2() const;
void Value2( int data );

private:
X& pX;
};

X::X( int data1, int data2 ) : data1( data1), data2( data2 )
{
}

Y X::Get() const
{
return Y( const_cast< X& >( *this ));
}

Y X::Set()
{
return Y( *this );
}

Y::Y( X& p ) : pX( p )
{
}

Y::Y( const Y& r ) : pX( r.pX )
{
}

int Y::Value1() const
{
return pX.data1;
}

void Y::Value1( int data )
{
pX.data1 = data;
}

int Y::Value2() const
{
return pX.data2;
}

void Y::Value2( int data )
{
pX.data2 = data;
}

int main()
{
int number1 = 0, number2 = 0;

// const X x( 5, 10 );
X x( 5, 10 );

number1 = x.Get().Value1();
number2 = x.Get().Value2();

x.Set().Value1( 50 ); // error if you declare const X x( 5, 10 )
x.Set().Value2( 100 ); // error if you declare const X x( 5, 10 )

number1 = x.Get().Value1();
number2 = x.Get().Value2();

return 0;
}
 
T

Tobias Müller

Nephi Immortal said:
Please check to make sure if my code is safe since I chose to define only
one class instead of two classes as const class and non-const class. Are
you positive sure if undefined behavior does not occur? I am told that
if I declare const_cast before undefined behavior will occur.

Is copy constructor in class Y safe?


class Y;

class X
{
friend class Y;

public:
X( int data1, int data2 );
Y Get() const;
Y Set();

private:
int data1;
int data2;
};

class Y
{
public:
Y( X& p );
Y( const Y& r );

int Value1() const;
void Value1( int data );

int Value2() const;
void Value2( int data );

private:
X& pX;
};

X::X( int data1, int data2 ) : data1( data1), data2( data2 )
{
}

Y X::Get() const
{
return Y( const_cast< X& >( *this ));
}

Y X::Set()
{
return Y( *this );
}

Y::Y( X& p ) : pX( p )
{
}

Y::Y( const Y& r ) : pX( r.pX )
{
}

int Y::Value1() const
{
return pX.data1;
}

void Y::Value1( int data )
{
pX.data1 = data;
}

int Y::Value2() const
{
return pX.data2;
}

void Y::Value2( int data )
{
pX.data2 = data;
}

int main()
{
int number1 = 0, number2 = 0;

// const X x( 5, 10 );
X x( 5, 10 );

number1 = x.Get().Value1();
number2 = x.Get().Value2();

x.Set().Value1( 50 ); // error if you declare const X x( 5, 10 )
x.Set().Value2( 100 ); // error if you declare const X x( 5, 10 )

number1 = x.Get().Value1();
number2 = x.Get().Value2();

return 0;
}

Const_casting a const object to a non-const reference is undefined
behavior. That means, the following (valid) snippet triggers undefined
behavior:

const X x(5,10);
int a = x.Get().value1();

In practice, this will probably work everywhere, since you implicitly cast
it immediately back to const X& when calling value1().

The real problem in your code is, that the following will compile without
errors:

const X x(5,10);
x.Get().value1(7);

This is "real" undefined behavior and confusing anyway. Consider the
following:

X x(5,10);
x.Set().value1(4);
x.Get().value1(4); // compiles and has the same effect as the line before.

The problem is, that you never ever have a const Y object, so the const
qualifier on Y::value1/2() is basically useless.

Now you can say that this ok because noone will ever do such a thing. I
don't agree, but if you do, then I have a solution to make at least the
non-pathologic cases free from undefined behavior:

class Y
{
public:
Y( const X& p ); // changed to const
//...
private:
const X& pX; // changed to const
};

// ...

void Y::Value1( int data )
{
const_cast<X&>(pX).data1 = data; // cast to non-const
}

void Y::Value2( int data )
{
const_cast<X&>(pX).data2 = data; // cast to non-const
}

Now I wonder what you actually want to achieve. What is the benefit over
some simple SetValue1()/GetValue1() methods directly in X? Those are safer,
simpler, and less confusing.

It looks even almost the same:
x.Set().value1(7);
int i = x.Get().value1();
vs.
x.SetValue1(7);
int i = x.GetValue1();

Are you trying to create some sort of EDSL? Or C#-like property syntax?

Tobi
 
A

Andrey Tarasevich

Please check to make sure if my code is safe since I chose to define only one class instead of two classes as const class and non-const class. Are you positive sure if undefined behavior does not occur? I am told that if I declare const_cast before undefined behavior will occur.

I'm not sure what you mean by "declare const_cast", since it doesn't
seem to make much sense. You can't declare `const_cast`.

Anyway, `const_cast` never produces undefined behavior. It can serve as
a tool that can help you to run into undefined behavior, since you can
use it to cast away constness from a const object and then attempt to
modify it. But `const_cast` itself does not produce undefined behavior.

In short, attempting to modify a const object causes undefined behavior.
Casting away constness by itself does not cause undefined behavior.
Is copy constructor in class Y safe?

What do you mean by "safe"? There's nothing wrong with the copy
constructor in your class `Y`. And there's nothing remarkable about it.
In fact, you don't even have to write that copy constructor explicitly,
since your explicit version does exactly the same thing as the
implicitly generated version would do.

Meanwhile, your code is not safe for reasons that have nothing to do
with copy constructor of `Y`. You code is not safe because it can cast
away the constness of `X` object in `X::Get`, pass it to the conversion
constructor of `Y` and then attempt to modify that `X` object though
method `Y::Value(int)`.
int main()
{
int number1 = 0, number2 = 0;

// const X x( 5, 10 );
X x( 5, 10 );

number1 = x.Get().Value1();
number2 = x.Get().Value2();

x.Set().Value1( 50 ); // error if you declare const X x( 5, 10 )
x.Set().Value2( 100 ); // error if you declare const X x( 5, 10 )

If `x` was a constant object, this code produces undefined behavior in
`Y::Value(int)`. If `x` was not a constant object, the code has defined
behavior.

But you know it already, judging by your comments. So, what is your
question is supposed to be about?
 
A

Andrey Tarasevich

Const_casting a const object to a non-const reference is undefined
behavior.

No, it isn't. `const_cast` never produces undefined behavior.
That means, the following (valid) snippet triggers undefined
behavior:

const X x(5,10);
int a = x.Get().value1();

No, this snippet is perfectly fine. There's absolutely nothing undefined
here. Even though this code casts away the constness of `x`, it makes no
attempts to modify `x`. The code is ugly, but the behavior is defined.
In practice, this will probably work everywhere, since you implicitly cast
it immediately back to const X& when calling value1().

The real problem in your code is, that the following will compile without
errors:

const X x(5,10);
x.Get().value1(7);

This is "real" undefined behavior and confusing anyway.

This piece of code does indeed produce undefined behavior, because
inside `Y::Value1(int)` it attempts to modify `x`, which happens to be a
const object.
 
R

Rui Maciel

Andrey said:
No, it isn't. `const_cast` never produces undefined behavior.

The following is taken from the C++ standard [5.2.11 7]:

<quote>
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-qualifier72 may produce undefined behavior
</quote>



Rui Maciel
 
V

Victor Bazarov

Andrey said:
No, it isn't. `const_cast` never produces undefined behavior.

The following is taken from the C++ standard [5.2.11 7]:

<quote>
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-qualifier72 may produce undefined behavior
</quote>

".. a write operation through {blah} may produce undefined behavior",
IOW, no write operation - no undefined behavior. The 'const_cast'
itself does not represent a write operation.

V
 
I

Ian Collins

Rui said:
Andrey said:
No, it isn't. `const_cast` never produces undefined behavior.

The following is taken from the C++ standard [5.2.11 7]:

<quote>
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-qualifier72 may produce undefined behavior
</quote>

That doesn't contradict what was written. The write operation results
in undefined behaviour, not the cast operation.
 
A

Andrey Tarasevich

No, it isn't. `const_cast` never produces undefined behavior.

The following is taken from the C++ standard [5.2.11 7]:

<quote>
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-qualifier72 may produce undefined behavior
</quote>

As I stated in my answer, it is the attempt to modify a constant object
("... a write operation ...") that produces undefined behavior.

`const_cast` itself is a way to create "the pointer, lvalue or pointer
to data member" mentioned in the above quote. By itself it does not make
any "write operations" through the resultant value. And it does not
cause any undefined behavior.
 
N

Nephi Immortal

Const_casting a const object to a non-const reference is undefined

behavior. That means, the following (valid) snippet triggers undefined

behavior:



const X x(5,10);

int a = x.Get().value1();



In practice, this will probably work everywhere, since you implicitly cast

it immediately back to const X& when calling value1().



The real problem in your code is, that the following will compile without

errors:



const X x(5,10);

x.Get().value1(7);



This is "real" undefined behavior and confusing anyway. Consider the

following:



X x(5,10);

x.Set().value1(4);

x.Get().value1(4); // compiles and has the same effect as the line before..



The problem is, that you never ever have a const Y object, so the const

qualifier on Y::value1/2() is basically useless.



Now you can say that this ok because noone will ever do such a thing. I

don't agree, but if you do, then I have a solution to make at least the

non-pathologic cases free from undefined behavior:



class Y

{

public:

Y( const X& p ); // changed to const

//...

private:

const X& pX; // changed to const

};



// ...



void Y::Value1( int data )

{

const_cast<X&>(pX).data1 = data; // cast to non-const

}



void Y::Value2( int data )

{

const_cast<X&>(pX).data2 = data; // cast to non-const

}
If all data members in this class are constant, and you want to modify someof them when necessary. The programmers always use const_cast since mutable did not exist in earlier C++ Compiler. They are able to use mutable so that they are not tempted to use const_cast by mistake.
Now I wonder what you actually want to achieve. What is the benefit over

some simple SetValue1()/GetValue1() methods directly in X? Those are safer,

simpler, and less confusing.



It looks even almost the same:

x.Set().value1(7);

int i = x.Get().value1();

vs.

x.SetValue1(7);

int i = x.GetValue1();

Both methods are your preferred choice. I choose to add proxy class with main class because Set() and Get() call proxy class’ member functions are easier to be readable than whole word and/or underscore. In other words, when you use Visual C++ and you type the dot (.) between class’ variable and member functions, the pop-up menu appears to the screen giving you many choices to select member functions rather than read each member functions from code.

I use C++, but NOT C#. That is why I go to C++ newsgroups.
 
R

Rui Maciel

Andrey said:
As I stated in my answer, it is the attempt to modify a constant object
("... a write operation ...") that produces undefined behavior.

`const_cast` itself is a way to create "the pointer, lvalue or pointer
to data member" mentioned in the above quote. By itself it does not make
any "write operations" through the resultant value. And it does not
cause any undefined behavior.

That's not right. What may lead to undefined behavior is having used
const_cast to remove access restrictions which should always be there and
should never be taken away. If the const qualifier isn't used irresponsibly
then the compiler naturally catches the attempt to call non-const member
functions on a const object, as is expected from him.

Let's be honest here: if you one day stumbled on a bug caused by this sort
of problem, you would never claim that the const_cast was perfectly fine,
and that the entire problem was caused by how the "write operation" has been
poorly designed so that it fails to work when its access restrictions were
removed.


Rui Maciel
 
R

Rui Maciel

Ian said:
That doesn't contradict what was written. The write operation results
in undefined behaviour, not the cast operation.

What leads to the undefined behavior is the removal of the safeguards
against abusing that write operation. After intentionally removing a
failsafe, if executing an operation, which by design should never be
executed, happens to unleash havoc then it isn't possible to blame that
operation for the problems. The responsibility lies nowhere else besides
how the failsafe has been removed, and how that left the system in an
unstable state.


Rui Maciel
 
I

Ian Collins

Rui said:
That's not right.

Oh yes it is!
What may lead to undefined behavior is having used
const_cast to remove access restrictions which should always be there and
should never be taken away.

No it isn't.

It's not uncommon to have to pass something like a string literal or the
result of std::string's c_str() to a C function that (mis)uses char* for
a parameter. One example that pops up a few times in my code base is
the UNIX putenv() function.
If the const qualifier isn't used irresponsibly
then the compiler naturally catches the attempt to call non-const member
functions on a const object, as is expected from him.

Let's be honest here: if you one day stumbled on a bug caused by this sort
of problem, you would never claim that the const_cast was perfectly fine,
and that the entire problem was caused by how the "write operation" has been
poorly designed so that it fails to work when its access restrictions were
removed.

Some of us have to live with crusty C interfaces....
 
R

Rui Maciel

Victor said:
".. a write operation through {blah} may produce undefined behavior",
IOW, no write operation - no undefined behavior. The 'const_cast'
itself does not represent a write operation.

The const_cast removes the qualifier which was put in place to ensure that
the system was never left in an unstable state. Once the qualifier was
removed, there is no guarantee that things won't start to blow up. In its
nature, this is no different than intentionally writing on memory segments
which weren't allocated and may or may not be a part of any other object.


Rui Maciel
 
I

Ian Collins

Rui said:
What leads to the undefined behavior is the removal of the safeguards
against abusing that write operation. After intentionally removing a
failsafe, if executing an operation, which by design should never be
executed, happens to unleash havoc then it isn't possible to blame that
operation for the problems. The responsibility lies nowhere else besides
how the failsafe has been removed, and how that left the system in an
unstable state.

See the example use of putenv() here:

http://pubs.opengroup.org/onlinepubs/000095399/

and make it work in C++ without copying the string literal. That's just
one of many I come across most days....
 
R

Rui Maciel

Ian said:
No it isn't.

It's not uncommon to have to pass something like a string literal or the
result of std::string's c_str() to a C function that (mis)uses char* for
a parameter. One example that pops up a few times in my code base is
the UNIX putenv() function.

The standard states quite clearly that it depends on both the object type
and on the write operation. Using a const_cast doesn't always lead to
undefined behavior. If it did then there would be no point in having it to
begin with.

If the const_cast is used to remove access restrictions which were put in
place to ensure that the system remains stable, removing those access
restrictions places the system in an unstable state. No one can blame an
operation for a problem caused by eliminating the restrictions that were in
place to make sure it would be impossible to perform.


Rui Maciel
 
R

Rui Maciel

Ian said:
See the example use of putenv() here:

http://pubs.opengroup.org/onlinepubs/000095399/

and make it work in C++ without copying the string literal. That's just
one of many I come across most days....

The standard quite clearly states that it depends on the object type and the
write operation.

This is even more relevant in C++11, as since this standard revision the
const qualifier defines the thread safety of an object. The standard states
[17.6.5.9] the following:

<quote>
A C ++ standard library function shall not directly or indirectly modify
objects (1.10) accessible by threads other than the current thread unless
the objects are accessed directly or indirectly via the function’s non-
const arguments, including this.
</quote>


By calling const_cast to cast away a const qualifier, you tell the compiler
that thread-unsafe operations are actually thread safe. Then, when those
operations are called, operations which otherwise would not be possible to
call, nasty stuff happens.

Here's Herb Sutter's presentation on this subject:

http://channel9.msdn.com/posts/C-and-Beyond-2012-Herb-Sutter-You-dont-know-
blank-and-blank

you can watch from 10m30s to cut to the chase.


Rui Maciel
 
T

Tobias Müller

Rui Maciel said:
What leads to the undefined behavior is the removal of the safeguards
against abusing that write operation. After intentionally removing a
failsafe, if executing an operation, which by design should never be
executed, happens to unleash havoc then it isn't possible to blame that
operation for the problems. The responsibility lies nowhere else besides
how the failsafe has been removed, and how that left the system in an
unstable state.

Thanks for the pointer to the standard, my memories were wrong.

Actually, I agree with Ian, the const_cast per se does not lead directly to
undefined behavior. Just because it may lead to undefined behavior if
sometimes in the future a programmer abuses it, it doesn't mean that it is
undefined behavior now.

Tobi
 
B

Bart van Ingen Schenau

The const_cast removes the qualifier which was put in place to ensure
that the system was never left in an unstable state. Once the qualifier
was removed, there is no guarantee that things won't start to blow up.
In its nature, this is no different than intentionally writing on memory
segments which weren't allocated and may or may not be a part of any
other object.

Do I understand you correctly that you are saying this gives UB:

const char* my_strchr(const char* str, char c)
{
/* my replacement implementation of strchr(): */
return str;
}
char* my_strchr(char* str, char c)
{
return cons_cast<char*>(my_strchr(cont_cast<const char*>(str), c));
}

Note that I use const_cast *twice* in the second function, one of them to
remove a const-qualification.
Rui Maciel

Bart v Ingen Schenau
 
A

Andrey Tarasevich

That's not right. What may lead to undefined behavior is having used
const_cast to remove access restrictions which should always be there and
should never be taken away.If the const qualifier isn't used irresponsibly
then the compiler naturally catches the attempt to call non-const member
functions on a const object, as is expected from him.

That is probably very sound reasoning, but that is not how the language
is defined. And the language has its reasons to be defined the way it is
defined.

In the current C++ language removing constness from an access path is a
perfectly valid operation, which never causes undefined behavior, even
if the object on the ather end of that access path is actually `const`.
Let's be honest here: if you one day stumbled on a bug caused by this sort
of problem, you would never claim that the const_cast was perfectly fine,
and that the entire problem was caused by how the "write operation" has been
poorly designed so that it fails to work when its access restrictions were
removed.

Probably. But that's the reality of C++ programming.

In any case, the matter of bad/good programming practices and the matter
of formal language specifications are two different matters. And the
concept of "undefined behavior" belongs to the realm of the latter.
 
A

Andrey Tarasevich

What leads to the undefined behavior is the removal of the safeguards
against abusing that write operation.

Well, ultimately that's no different that stating the the very design of
C++ language is what _really_ leads to all kinds of undefined behavior.
And we have no shortage of individuals who just love to spread this sort
of wisdom around various places on the net and outside of it.

While this might be an interesting topic for some people, it is not
really what the question is about.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top