Modifying non-const members of const object - c++

I do know that modifying an object declared as constant is an UB. What about more complex example mentioned in the title?
class Foo
{
public:
Foo ( void ) { }
int data;
};
int main ( void )
{
const Foo foo;
const_cast<Foo&>(foo).data = 0; // UB?
return 0;
}
data is declared as non-const so it's ok to modify it. But the foo is declared as const. So it seems we can not modify it. Thus I believe that an UB is invoked here. Am I right?
UPDATE: So it comes out that it's actually an UB. This means that all the classes which have fake constant members modifying mutable members produce an UB on constant instances.
class Foo
{
public:
mutable int data;
Foo ( void ) { }
void foo ( void ) const
{
some_modifications_of_data();
}
};
const Foo foo;
foo.foo(); // UB?
Does it mean that if you design this kind of class you must explicitly mention that under no circumstances nobody can call this method on a constant instance?

Using const_cast to modify data in a const data structure is indeed undefined behaviour. The exception is items marked mutable. The whole point of these values are that they are modifiable even when the rest of the object is const. It really means "but this one is not const".
Since nearly all of const is about the compiler detecting modification, although technically, the compiler is allowed place some const variables in "non-writeable memory". The mutable keyword is there to allow "bypass" of the constness, so the compiler will NOT put a const object into memory that is non-writeable if it has a mutable component, and of course, it won't "object" to const objects being modified in it's mutable components - even inside a const function.

Related

Allowing a function to mutate a const object's member variable

This is related to the (currently) closed question I asked earlier: Can you mutate an object of custom type when it's declared as constant?
Suppose we have something that looks like the following:
class test
{
public:
test() : i{4}, ptr{&i} {};
int i;
int *ptr;
int *get_ptr() const {return ptr;}
};
void func(const test &t, int j) {
*(t.get_ptr()) = j;
// auto ptr = t.get_ptr();
// *ptr = j;
}
int main(int argc, char const *argv[])
{
test t;
std::cout << t.i << std::endl;
func(t, 5);
std::cout << t.i << std::endl;
}
We have this func that takes in a const test &. When I see this signature (and if I didn't look at the implementation of the function), it makes me want to assume that nothing in t will get modified; however, the member variable i is able to be modified through the ptr member variable, as we see here.
I don't usually write code that end up working this way, so I'm wondering if code like this is discouraged?
Furthermore, is it reasonable to assume (most of the time) that an object declared as const will not be mutated?
Yes code like this is definitely discouraged. It completely ignores the reason we have the const keyword in the first place. The function is deliberately modifying something that it is advertising it will not modify
That means that whoever wrote the get_ptr() function messed up because they declared it const but let it return a pointer to non-const object (so one that can be changed, defeating the purpose of declaring the function const)
If whatever get_ptr() returns could properly be modified by such a function then it should be an implementation detail, a private (or protected) variable marked with the mutable keyword.
test::get_ptr() should have two overloads.
const int* get_ptr() const { return ptr; }
int* get_ptr() { return ptr; }
If func() wants to change the test object given to it then it should take test&, not const test&
The key is here:
int *get_ptr() const {return ptr;}
You define get_ptr as const which is akin to saying "get_ptr is not going to change any attributes of the class". And it doesn't. It returns the value of the attribute ptr, which is a int*, and points to a mutable instance of an int which happens to be an attribute of the class as well.
The compiler has no way of knowing this, so from the compiler's perspective the promise was kept. However in reality you're circumventing the const qualifier and allowing to mutate an otherwise immutable attribute.
Not the best of coding practices, but whoever writes such code should, obviously, not expect for i to remain as it was set in the class methods if get_ptr is ever called.

Is it legal to cast const away of a non-static const field with guaranteed non const allocation

I have the following code which seems to work always (msvc, gcc and clang).
But I'm not sure if it is really legal. In my framework my classes may have "two constructors" - one normal C++ constructor which does simple member initialization and an additional member function "Ctor" which executes additional initialization code. It is used to allow for example calls to virtual functions. These calls are handled by a generic allocation/construction function - something like "make_shared".
The code:
#include <iostream>
class Foo
{
public:
constexpr Foo() : someConstField(){}
public:
inline void Ctor(int i)
{
//use Ctor as real constructor to allow for example calls to virtual functions
const_cast<int&>(this->someConstField) = i;
}
public:
const int someConstField;
};
int main()
{
//done by a generic allocation function
Foo f;
f.Ctor(12); //after this call someConstField is really const!
//
std::cout << f.someConstField;
}
Modifying const memory is undefined behaviour. Here that int has already been allocated in const memory by the default constructor.
Honestly I am not sure why you want to do this in the first place. If you want to be able to initalise Foo with an int just create an overloaded constructor:
...
constexpr Foo(int i) : someConstField{i} {}
This is completely legal, you are initalising the const memory when it is created and all is good.
If for some reason you want to have your object initalised in two stages (which without a factory function is not a good idea) then you cannot, and should not, use a const member variable. After all, if it could change after the object was created then it would no longer be const.
As a general rule of thumb you shouldn't have const member variables since it causes lots of problems with, for example, moving an object.
When I say "const memory" here, what I mean is const qualified memory by the rules of the language. So while the memory itself may or may not be writable at the machine level, it really doesn't matter since the compiler will do whatever it likes (generally it just ignores any writes to that memory but this is UB so it could do literally anything).
No.
It is undefined behaviour to modify a const value. The const_cast itself is fine, it's the modification that's the problem.
According to 7.1.6.1 in C++17 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.
And there is an example (similar to yours, except not for class member):
const int* ciq = new const int (3); // initialized as required
int* iq = const_cast<int*>(ciq); // cast required
*iq = 4; // undefined: modifies a const object
If your allocation function allocates raw memory, you can use placement new to construct an object at that memory location. With this you must remember to call the destructor of the object before freeing the allocation.
Small example using malloc:
class Foo
{
public:
constexpr Foo(int i) : someConstField(i){}
public:
const int someConstField;
};
int main()
{
void *raw_memory = std::malloc(sizeof(Foo));
Foo *foo = new (raw_memory) Foo{3}; // foo->someConstField == 3
// ...
foo->~Foo();
std::free(foo);
}
I suggest, that you use the constructor to avoid the const cast. You commented, that after your call of Ctor the value of someConstField will remain const. Just set it in the constructor and you will have no problems and your code becomes more readable.
#include <iostream>
class Foo
{
public:
constexpr Foo(int i) : someConstField(Ctor(i)){}
int Ctor(); // to be defined in the implementation
const int someConstField;
};
int main()
{
Foo f(12);
std::cout << f.someConstField;
}

const_cast 'this' in const method to assign 'this' to outer variable?

Have a look at the following code:
struct Foo;
Foo* bar;
struct Foo {
void func() const {
bar = this;
}
}
int main() {
Foo().func();
}
This does not work as bar won't accept a const Foo*. To get around this, const_cast could be used:
struct Foo {
void func() const {
bar = const_cast<Foo*>(this);
}
}
Is this safe? I'm very cautious when it comes to using const_cast, but in this case it seems legit to me.
No, this is potentially dangerous.
func() is marked const which means that it can be called by a const object:
const Foo foo;
foo.func();
Because this is const Foo*.
If you const_cast away the const you end up with a Foo* to a const object. This means that any modification to that object through the non-const pointer (or through any copy of that pointer, bar in this case) will get you undefined behavior since you are not allowed to modify a const object (duh).
Plus the obvious problem is that you're lying to the consumer of class Foo by saying func() won't modify anything while you're doing the opposite.
const_cast is almost never correct and this seems like an XY-problem to me.
If by "safe" you mean "not undefined behavior" then yes, it is safe. The value of bar will point to the created object as you'd expect.
However, const_cast is generally not recommended because it breaks the convention that a const thing will not be changed, and can easily produce undefined behavior (see this comment below). In this case you can simply do:
struct Foo {
void func() const {
bar = const_cast<Foo*>(this);
bar->modify();
}
void modify() { ... }
}
And you will be modifying the object in a const method, which is unexpected in general and undefined behavior if the instance of Foo on which the method is called was initially declared as const. However, it is up to you to get the logic right. Just note that everyone else will expect const things to be const, including the standard library, and usually (if not always) a better code design is possible.
Under some circumstances it is safe, namely when you have a non-const Foo object. If you have a const Foo object however, you're not allowed to modify it, and the compiler will not catch the bug because of the cast. So it is not a good idea to use this.
Note that in your example Foo is a temporary which gets destroyed on the next line of main().

Does this code subvert the C++ type system?

I understand that having a const method in C++ means that an object is read-only through that method, but that it may still change otherwise.
However, this code apparently changes an object through a const reference (i.e. through a const method).
Is this code legal in C++?
If so: Is it breaking the const-ness of the type system? Why/why not?
If not: Why not?
Note 1: I have edited the example a bit, so answers might be referring to older examples.
Edit 2: Apparently you don't even need C++11, so I removed that dependency.
#include <iostream>
using namespace std;
struct DoBadThings { int *p; void oops() const { ++*p; } };
struct BreakConst
{
int n;
DoBadThings bad;
BreakConst() { n = 0; bad.p = &n; }
void oops() const { bad.oops(); } // can't change itself... or can it?
};
int main()
{
const BreakConst bc;
cout << bc.n << endl; // 0
bc.oops(); // O:)
cout << bc.n << endl; // 1
return 0;
}
Update:
I have migrated the lambda to the constructor's initialization list, since doing so allows me to subsequently say const BreakConst bc;, which -- because bc itself is now const (instead of merely the pointer) -- would seem to imply (by Stroustrup) that modifying bc in any way after construction should result in undefined behavior, even though the constructor and the caller would have no way of knowing this without seeing each others' definitions.
The oops() method isn't allowed to change the constness of the object. Furthermore it doesn't do it. Its your anonymous function that does it. This anonymous function isn't in the context of the object, but in the context of the main() method which is allowed to modify the object.
Your anonymous function doesn't change the this pointer of oops() (which is defined as const and therefore can't be changed) and also in no way derives some non-const variable from this this-pointer. Itself doesn't have any this-pointer. It just ignores the this-pointer and changes the bc variable of the main context (which is kind of passed as parameter to your closure). This variable is not const and therefore can be changed. You could also pass any anonymous function changing a completely unrelated object. This function doesn't know, that its changing the object that stores it.
If you would declare it as
const BreakConst bc = ...
then the main function also would handle it as const object and couldn't change it.
Edit:
In other words: The const attribute is bound to the concrete l-value (reference) accessing the object. It's not bound to the object itself.
You code is correct, because you don't use the const reference to modify the object. The lambda function uses completely different reference, which just happen to be pointing to the same object.
In the general, such cases does not subvert the type system, because the type system in C++ does not formally guarantee, that you can't modify the const object or the const reference. However modification of the const object is the undefined behaviour.
From [7.1.6.1] The cv-qualifiers:
A pointer or reference to a cv-qualified type need not actually point
or refer to a cv-qualified object, but it is treated as if it does; a
const-qualified access path cannot be used to modify an object even if
the object referenced is a non-const object and can be modified through
some other access path.
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.
I already saw something similar. Basically you invoke a cost function that invoke something else that modifies the object without knowing it.
Consider this as well:
#include <iostream>
using namespace std;
class B;
class A
{
friend class B;
B* pb;
int val;
public:
A(B& b);
void callinc() const;
friend ostream& operator<<(ostream& s, const A& a)
{ return s << "A value is " << a.val; }
};
class B
{
friend class A;
A* pa;
public:
void incval() const { ++pa->val; }
};
inline A::A(B& b) :pb(&b), val() { pb->pa = this; }
inline void A::callinc() const { pb->incval(); }
int main()
{
B b;
const A a(b); // EDIT: WAS `A a(b)`
cout << a << endl;
a.callinc();
cout << a << endl;
}
This is not C++11, but does the same:
The point is that const is not transitive.
callinc() doesn't change itself a and incval doesn't change b.
Note that in main you can even declare const A a(b); instead of A a(b); and everything compile the same.
This works from decades, and in your sample you're just doing the same: simply you replaced class B with a lambda.
EDIT
Changed the main() to reflect the comment.
The issue is one of logical const versus bitwise const. The compiler
doesn't know anything about the logical meaning of your program, and
only enforces bitwise const. It's up to you to implement logical const.
This means that in cases like you show, if the pointed to memory is
logically part of the object, you should refrain from modifying it in a
const function, even if the compiler will let you (since it isn't part
of the bitwise image of the object). This may also mean that if part of
the bitwise image of the object isn't part of the logical value of the
object (e.g. an embedded reference count, or cached values), you make it
mutable, or even cast away const, in cases where you modify it without
modifying the logical value of the object.
The const feature merely helps against accidental misuse. It is not designed to prevent dedicated software hacking. It is the same as private and protected membership, someone could always take the address of the object and increment along the memory to access class internals, there is no way to stop it.
So, yes you can get around const. If nothing else you can simply change the object at the memory level but this does not mean const is broken.

Do class member reference variables have in-built "const-correctness"?

struct A {
int &r;
A (int &i) : r(i) {}
void foo () const {
r = 5; // <--- ok
}
};
The compiler doesn't generate any error at r = 5;.
Does it mean that &r is already const-correct being a reference (logical equivalent of int* const) ? [Here is one related question.]
I'm not sure exactly what you mean by "already const-correct", but:
Assigning to r is the same as assigning to whatever thing was passed into the constructor of A. You're not modifying anything in the instance of A when you do this, so the fact that foo is declared const isn't an obstacle. It's very much as if you'd done this:
struct A {
int * r;
A (int * i) : r(i) {}
void foo () const { *r = 5; }
}
The fact that foo is const means that it doesn't modify anything in the A instance it's called on. There's no conflict between that and having it modify other data it was supplied with.
Of course if you happened to arrange for r to be a reference to some member of A then calling foo would modify the instance of A after all. The compiler can't catch all possible ways in which constness of a member function might be violated; when you declare a member function const you're promising that it doesn't engage in any such subterfuge.
Yes, it's the logical equivalent of int* const.
You may want to create and use appropriately qualified accessors in this case to prevent unwanted alterations to the value r references.
I interpret a const member function as implicitly inserting const just to the left of every data member that doesn't already have such a qualifier. That const is already there implicitly for references (int & const r; is illegal syntax). In other words, references are "already const-correct" to use your nomenclature.
It would be nice if the const qualifier on a member function had the affect of inserting const in every possible valid position for every data member (e.g., data member int ** foo; acts like int const * const * const foo; in a const member function), but that isn't what happens, and it isn't what the standard says will happen.