const_cast 'this' in const method to assign 'this' to outer variable? - c++

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().

Related

What good is the c++'s `const` promise?

I am trying to understand c++'s const semantics more in depth but I can't fully understand what really the constness guarantee worth is.
As I see it, the constness guarantees that there will be no mutation, but consider the following (contrived) example:
#include <iostream>
#include <optional>
#include <memory>
class A {
public:
int i{0};
void foo() {
i = 42;
};
};
class B {
public:
A *a1;
A a2;
B() {
a1 = &a2;
}
void bar() const {
a1->foo();
}
};
int main() {
B b;
std::cout << b.a2.i << std::endl; // output is 0
b.bar();
std::cout << b.a2.i << std::endl; // output is 42
}
Since bar is const, one would assume that it wouldn't mutate the object b. But after its invocation b is mutated.
If I write the method foo like this
void bar() const {
a2.foo();
}
then the compiler catches it as expected.
So it seems that one can fairly easily circumvent the compiler with pointers. I guess my main question is, how or if I can be 100% sure that const methods won't cause any mutation to the objects they are invoked with? Or do I have completely false expectations about const?
Why does c++ allow invocation of non-const methods over pointers in const methods?
EDIT:
thanks to Galik's comment, I now found this:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4372.html
Well, this was exactly what I was looking for! Thanks!
And I find Yakk's answer also very helpful, so I'll accept his answer.
const tells the caller "this shouldn't mutate the object".
const helps the implementor with some errors where accidentally mutating state generates errors unless the implementor casts it away.
const data (not references, actual data) provides guarantees to the compiler that anyone who modifies this data is doing undefined behaviour; thus, the compiler is free to assume that the data is never modified.
const in the std library makes certain guarantees about thread safety.
All of these are uses of const.
If an object isn't const, anyone is free to const_cast away const on a reference to the object and modify it.
If an object is const, compilers will not reliably diagnose you casting away const, and generating undefined behavior.
If you mark data as mutable, even if it is also marked as const it won't be.
The guarantees that the std provides based off const are limited by the types you in turn pass into std following those guarantees.
const doesn't enforce much on the programmer. It simply tries to help.
No language can make a hostlie programmer friendly; const in C++ doesn't try. Instead, it tries to make it easier to write const-correct code than to write const-incorrect code.
Constness by itself doesn't guarantee you anything. It only takes away rights of specific code to mutate an object through a specific reference. It doesn't take away rights of other code to mutate the same object through other references, right under your feet.
So it seems that one can fairly easily circumvent the compiler with pointers.
That is indeed true.
I guess my main question is, how or if I can be 100% sure that const methods won't cause any mutation to the objects they are invoked with?
The language guarantees that only in a local sense.
Your class is, indirectly, the same as the following:
struct Foo
{
int* ptr;
Foo() : ptr(new int(0)) {};
void bar() const { *ptr = 10; }
};
When you use:
Foo f;
f.bar();
the member variable of f did not change since the pointer still points to the location after the call to f.bar() as it did before the call. In that sense, f did not change. But if you extend the "state" of f to include the value of what f.ptr points to, then the state of f did change. The language does not guarantee against such changes.
It's our job, as designers and developers, to document the "const" semantics of the types we create and provide functions that preserve those semantics.
Or do I have completely false expectations about const?
Perhaps.
When a class method is declared as const, that means the implicit this pointer inside the method is pointing at a const object and thus the method cannot alter any of the members of that object (unless they are explicitly declared as mutable, which is not the case in this example).
The B constructor is not declared as const, so this is of type B*, ie a pointer to a non-const B object. So a1 and a2 are non-const, and a1 is declared as a pointer to a non-const A object, so the compiler allows a1 to be pointed at a2.
bar() is declared as const, so this is of type const B*, ie a pointer to a const B object.
When bar() calls a1->foo(), a1 is const by virtue of this pointing at a const B object, so bar() can't change a1 to point at something else. But the A object that a1 is pointing at is still deemed to be non-const by virtue of a1's declaration, and foo() is not declared as const, so the compiler allows the call. However, the compiler can't validate that a1 is actually pointing at a2, a member of B that is supposed to be const inside of bar(), so the code breaks the const contract and has undefined behavior.
When bar() tries to call a2.foo() instead, a2 is const by virtue of this pointing at a const B object, but foo() is not declared as const, so the compiler fails the call.
const is just a safety catch for well-behaving code. It does not stop you from shooting yourself in the foot by using misbehaving code.
This is a correct observation. In const-qualified functions (bar in your example) all data members of the class are behaving as if they are const data members when accessed from this function. With pointers, it means that the pointer itself is constant, but not the object it points to. As a matter of fact, your example can be very much simplified into:
int k = 56;
int* const i = &k;
*i = 42;
There is a big difference between pointer to constant object and constant pointer, and one needs to understand it, so that 'promises', which were not given in the first place, would not seem to be broken.

Modifying non-const members of const object

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.

Is return by value always const?

This code does not compile:
class C {};
void foo (C& c) {}
C bar() { return C(); }
int main()
{
foo(bar());
}
Compilation error (GCC 4.1.2) in line foo(bar()):
invalid initialization of non-const reference of type 'C&'
from a temporary of type 'C'
As bar() returns a mutable object, it should compile...
Why C++ does not allow this above code?
EDIT: I have summarize in an answer below all good ideas from all answers ;-)
The applicable rule here is that you can't create a non-const reference to a temporary object. If foo was declared as foo(const C&) the code would be okay.
The temporary object itself is not const, though; you can call non-const member functions on it, e.g., bar().non_const_member_function().
With C++11, foo can be written to take an rvalue reference; in that case, the call would be okay:
void foo(C&&);
foo(bar()); // okay
It's because the value returned by bar is a temporary value. As it's existence is temporary, you can't use a pointer or reference to that.
However, if you store a copy of that temporary, as in your second change, you no longer pass a reference to a temporary object to foo, but a reference to a real tangible object. And in the first case, when you change to a reference to a constant object, the compiler makes sure the temporary object stays around long enough (as per the C++ specification).
The issue is not with the declaration of bar but with that of foo. foo takes a non-const reference, and temporaries can only bind to const references (which then extends the lifetime of the temporary to match that of the reference it is bound to).
Allowing a non-const reference to bind to a temporary doesn't make much sense. A non-const reference implies that it will modify whatever object is bound to it. Modifying a temporary serves no purpose since its lifetime is limited and the changes will be lost as soon as it goes out of scope.
Modifiable (lvalue-)references do not bind to temporary values. However, const-references do bind to temporary values. It has nothing to do with whether the object returned by value is const or not; it's simply a matter of whether the expression is temporary or not.
For example, the following is valid:
struct C { void i_am_non_const() {} };
int main()
{
bar().i_am_non_const();
}
It is a design choice. There is nothing inherently impossible here. Just a design choice.
In C++11, you have a third alternative which is also superior alternative:
void foo(C && c) {}
That is, use rvalue-references.
It's not const, but it is a temporary rvalue. As such, it can't bind to a non-const lvalue reference.
It can bind to a const or rvalue reference, and you can call member functions (const or not) on it:
class C { void f(); };
void foo_const(C const &);
void foo_rvalue(C &&);
foo_const( bar() ); // OK
foo_rvalue( bar() ); // OK
bar().f(); // OK
The real, hard truth is that it makes no sense to get a reference to a temporary value.
The big point of passing an object by reference is that it allows you to modify its state. However, in the case of a temporary, by its very nature, it would not be particularly helpful to be able to modify it, since you have no way of getting another reference to it later in your code to see the changes.
However, this is somewhat different in the case you have a const reference. Since you'll only ever read from a const reference, it makes total sense to be able to use temporaries there. This is why the compiler will "hack" around it for you, and give a more permanent address to temporaries that you want to "turn" into const references.
So, the rule is that you cannot get a non-const reference to a temporary value. (This slightly changed with C++11, where we have a new type of references that serve this exact purpose, but methods are expected to deal with those in a special way.)
Thank you all for your answers :-)
Here I gather your good ideas ;-)
Answer
Return by value is not const. For example, we can call non-const member functions of return by value:
class C {
public:
int x;
void set (int n) { x = n; } // non-const function
};
C bar() { return C(); }
int main ()
{
bar.set(5); // OK
}
But C++ does not allow non-const references to temporary objects.
However C++11 allow non-const rvalue-references to temporary objects. ;-)
Explanation
class C {};
void foo (C& c) {}
C bar() { return C(); }
//bar() returns a temporary object
//temporary objects cannot be non-const referenced
int main()
{
//foo() wants a mutable reference (i.e. non-const)
foo( bar() ); // => compilation error
}
Three fixes
Change foo declaration
void foo (const C& c) {}
Use another object
int main()
{
C c;
foo( c = bar() );
}
Use C++11 rvalue-reference
void foo(C && c) {}
Moreover
To confirm temporary objects are const, this above source code fails for the same reason:
class C {};
void foo(C& c) {}
int main()
{
foo( C() );
}

Why do I have to cast this?

I've condensed this problem to a small representative sample:
import std.stdio;
class Foo
{
private int f;
}
class State
{
private Foo foo;
const Foo getFoo()
{
return foo; // This line here.
}
}
void main()
{
auto s = new State;
writeln(s.getFoo());
}
I put that code in test.d.
$ gdmd test.d
test.d:13: Error: cannot implicitly convert expression (this.foo) of type const(Foo) to test.Foo
I understand that it's telling me to cast the return value with cast(test.Foo)foo, but why? Why does it interpret the member to be of type const(Foo) and why does it need me to cast away that const? I get the feeling that I'm doing something horribly wrong here.
const Foo getFoo()
is the same as
Foo getFoo() const
It makes the invisible this parameter const. Because const is transitive in D, that means that when you try and return a member variable from this, it's going to be const as well. If foo were a value type, then it would just copy it, and then returning a mutable Foo wouldn't be a problem, because it wouldn't affect the original. But Foo is a class, and therefore is a reference type. So, returning foo is returning a reference to the exact same object that State holds. No copy is made. And so it must be const - otherwise you would be violating the constness of the this parameter.
And no, casting away const is not a good solution. As discussed in this question, casting away const and then mutating the value is effectively illegal in D. You're violating the type system when you do that. The compiler will let you do it, but if you're taking your life into your own hands when you do. It's especially bad if the underlying object is actually immutable, because you can get a segfault in that case. You only cast away const if you absolutely have to, and you never mutate it unless you really know what you're doing.
No, the correct solution is to make the return type const:
const(Foo) getFoo() const
Now, you can return foo and use it. If you want a mutable Foo, then you either have to not have getFoo be const, or you have to have getFoo return a copy of foo. e.g.
Foo getFoo() const
{
//This would be cleaner if you provided a clone/dup function
//of some kind on Foo.
auto retval = new Foo;
retval.f = foo.f;
return retval;
}
The important thing for you to take away from here is that const in D is transitive. As Walter Bright puts it, "it's turtles all the way down." Once something is const, all parts of it are const, and don't cast away const to get around it unless you really know what you're doing. So, if you want to return a reference type which refers to a member variable from a const function, you either need to make the return type const or create a copy of it and return that.
I suspect that what you really want is this:
class State
{
private Foo foo;
// When `this` is const, return const(Foo).
// When `this` is immutable, return immutable(Foo).
// When `this` is mutable, return Foo.
inout(Foo) getFoo() inout
{
return foo;
}
}
Which is equivalent of:
class State
{
private Foo foo;
Foo getFoo()
{
return foo;
}
const(Foo) getFoo() const
{
// `this.foo` is of type const(Foo), as const is transitive.
return foo;
}
immutable(Foo) getFoo() immutable
{
// `this.foo` is of type immutable(Foo), as immutable is transitive.
return foo;
}
}
It interprets the member as const(Foo) because your method signature is const Foo getFoo(), which implies foo will not be mutable.
Remove the const and it should be fine.

Is the following C++ code equiv? (in a smart pointer implementation)

Code 1:
template<class T>
const PtrInterface<T>*
PtrInterface<T>::newRef() const {
PtrInterface<T>* me = (PtrInterface<T>*) this;
++me->references_;
//++this->references_;
return this;
}
Code 2:
template<class T>
const PtrInterface<T>*
PtrInterface<T>::newRef() const {
//PtrInterface<T>* me = (PtrInterface<T>*) this;
//++me->references_;
++this->references_;
return this;
}
Is there ever any situation where these two blocks of code will do different things?
Thanks!
Is there ever any situation where these two blocks of code will do different things?
Yes, when you are in a const method. Currently, the one with me invokes undefined behavior. Here's why:
As you know, when you call a member function, there is an implicit this pointer. The this pointer is const when a function is marked const. Take this for example:
struct foo
{
void method1(void);
void method2(void) const;
int i;
};
Implicitly, the compiler generates (by the way, this is simplified):
void foo::method1(foo* this);
void foo::method2(const foo* this) const;
So, are these two bodies the same?
foo* me = (foo*)this;
me->i = 1;
// and
this->i = 1;
The answer is it depends, and as stated earlier, it's dependent on the const-ness of the function. In a non-const function, they are the same:
void foo::method1(foo* this)
{
foo* me = (foo*)this; // this cast is redundant
me->i = 1;
// ...
this->i = 1;
}
But in a const function:
void foo::method2(const foo* this) const
{
foo* me = (foo*)this; // uh-oh! acts like const_cast
me->i = 1; // modifying a const_cast'd variable is undefined behavior
// ...
this->i = 1; // wouldn't compile
}
We end up stripping the const away. So, no, they aren't always the same. This is the peril of the C-style cast: it will find a way. By the way, casting const away in itself isn't undefined behavior; it's the modification of said variable that does it.
There is a sticky problem in your question though: your code shouldn't compile. Like in the commented code above, in your const method you shouldn't be able to modify reference_.
This is different if reference_ is mutable, which I'm guessing it might be (assuming you gave us compilable code.) In this case, I'm not certain if the first sample leads to undefined behavior, since it was mutable in the first place. I wouldn't take the chance though.
More or less everything GMan has said, except references_ need not be mutable. It could also be an object which has overridden operator++() to be a const member function.