Const method that modifies *this without const_cast
Consider the following:
int i = 3;
i
is an object, and it has the type int
. It is not cv-qualified (is not const
or volatile
, or both.)
Now we add:
const int& j = i;
const int* k = &i;
j
is a reference which refers to i
, and k
is a pointer which points to i
. (From now on, we simply combine "refer to" and "points to" to just "points to".)
At this point, we have two cv-qualified variables, j
and k
, that point to a non-cv-qualified object. This is mentioned in §7.1.5.1/3:
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. [Note: cv-qualifiers are supported by the type system so that they cannot be subverted without casting (5.2.11). ]
What this means is that a compiler must respect that j
and k
are cv-qualified, even though they point to a non-cv-qualified object. (So j = 5
and *k = 5
are illegal, even though i = 5
is legal.)
We now consider removing the const
from those:
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
This is legal (§refer to 5.2.11), but is it undefined behavior? No. See §7.1.5.1/4:
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.
Emphasis mine.
Remember that i
is not const
and that j
and k
both point to i
. All we've done is tell the type system to remove the const-qualifier from the type so we can modify the pointed to object, and then modified i
through those variables.
This is exactly the same as doing:
int& j = i; // removed const with const_cast...
int* k = &i; // ..trivially legal code
j = 5;
*k = 5;
And this is trivially legal. We now consider that i
was this instead:
const int i = 3;
What of our code now?
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
It now leads to undefined behavior, because i
is a const-qualified object. We told the type system to remove const
so we can modify the pointed to object, and then modified a const-qualified object. This is undefined, as quoted above.
Again, more apparent as:
int& j = i; // removed const with const_cast...
int* k = &i; // ...but this is not legal!
j = 5;
*k = 5;
Note that simply doing this:
const_cast<int&>(j);
*const_cast<int*>(k);
Is perfectly legal and defined, as no const-qualified objects are being modified; we're just messing with the type-system.
Now consider:
struct foo
{
foo() :
me(this), self(*this), i(3)
{}
void bar() const
{
me->i = 5;
self.i = 5;
}
foo* me;
foo& self;
int i;
};
What does const
on bar
do to the members? It makes access to them go through something called a cv-qualified access path. (It does this by changing the type of this
from T* const
to cv T const*
, where cv
is the cv-qualifiers on the function.)
So what are the members types during the execution of bar
? They are:
// const-pointer-to-non-const, where the pointer points cannot be changed
foo* const me;
// foo& const is ill-formed, cv-qualifiers do nothing to reference types
foo& self;
// same as const int
int const i;
Of course, the types are irrelevant, as the important thing is the const-qualification of the pointed to objects, not the pointers. (Had k
above been const int* const
, the latter const
is irrelevant.) We now consider:
int main()
{
foo f;
f.bar(); // UB?
}
Within bar
, both me
and self
point to a non-const foo
, so just like with int i
above we have well-defined behavior. Had we had:
const foo f;
f.bar(); // UB!
We would have had UB, just like with const int
, because we would be modifying a const-qualified object.
In your question, you have no const-qualified objects, so you have no undefined behavior.
And just to add an appeal to authority, consider the const_cast
trick by Scott Meyers, used to recycle a const-qualified function in a non-const function:
struct foo
{
const int& bar() const
{
int* result = /* complicated process to get the resulting int */
return *result;
}
int& bar()
{
// we wouldn't like to copy-paste a complicated process, what can we do?
}
};
He suggests:
int& bar(void)
{
const foo& self = *this; // add const
const int& result = self.bar(); // call const version
return const_cast<int&>(result); // take off const
}
Or how it's usually written:
int& bar(void)
{
return const_cast<int&>( // (3) remove const from result
static_cast<const foo&>(*this) // (1) add const to this
.bar() // (2) call const version
);
}
Note this is, again, perfectly legal and well-defined. Specifically, because this function must be called on a non-const-qualified foo
, we are perfectly safe in stripping the const-qualification from the return type of int& boo() const
.
(Unless someone shoots themselves with a const_cast
+ call in the first place.)
To summarize:
struct foo
{
foo(void) :
i(),
self(*this), me(this),
self_2(*this), me_2(this)
{}
const int& bar() const
{
return i; // always well-formed, always defined
}
int& bar() const
{
// always well-formed, always well-defined
return const_cast<int&>(
static_cast<const foo&>(*this).
bar()
);
}
void baz() const
{
// always ill-formed, i is a const int in baz
i = 5;
// always ill-formed, me is a foo* const in baz
me = 0;
// always ill-formed, me_2 is a const foo* const in baz
me_2 = 0;
// always well-formed, defined if the foo pointed to is non-const
self.i = 5;
me->i = 5;
// always ill-formed, type points to a const (though the object it
// points to may or may not necessarily be const-qualified)
self_2.i = 5;
me_2->i = 5;
// always well-formed, always defined, nothing being modified
// (note: if the result/member was not an int and was a user-defined
// type, if it had its copy-constructor and/or operator= parameter
// as T& instead of const T&, like auto_ptr for example, this would
// be defined if the foo self_2/me_2 points to was non-const
int r = const_cast<foo&>(self_2).i;
r = const_cast<foo* const>(me_2)->i;
// always well-formed, always defined, nothing being modified.
// (same idea behind the non-const bar, only const qualifications
// are being changed, not any objects.)
const_cast<foo&>(self_2);
const_cast<foo* const>(me_2);
// always well-formed, defined if the foo pointed to is non-const
// (note, equivalent to using self and me)
const_cast<foo&>(self_2).i = 5;
const_cast<foo* const>(me_2)->i = 5;
// always well-formed, defined if the foo pointed to is non-const
const_cast<foo&>(*this).i = 5;
const_cast<foo* const>(this)->i = 5;
}
int i;
foo& self;
foo* me;
const foo& self_2;
const foo* me_2;
};
int main()
{
int i = 0;
{
// always well-formed, always defined
int& x = i;
int* y = &i;
const int& z = i;
const int* w = &i;
// always well-formed, always defined
// (note, same as using x and y)
const_cast<int&>(z) = 5;
const_cast<int*>(w) = 5;
}
const int j = 0;
{
// never well-formed, strips cv-qualifications without a cast
int& x = j;
int* y = &j;
// always well-formed, always defined
const int& z = i;
const int* w = &i;
// always well-formed, never defined
// (note, same as using x and y, but those were ill-formed)
const_cast<int&>(z) = 5;
const_cast<int*>(w) = 5;
}
foo x;
x.bar(); // calls non-const, well-formed, always defined
x.bar() = 5; // calls non-const, which calls const, removes const from
// result, and modifies which is defined because the object
// pointed to by the returned reference is non-const,
// because x is non-const.
x.baz(); // well-formed, always defined
const foo y;
y.bar(); // calls const, well-formed, always defined
const_cast<foo&>(y).bar(); // calls non-const, well-formed,
// always defined (nothing being modified)
const_cast<foo&>(y).bar() = 5; // calls non-const, which calls const,
// removes const from result, and
// modifies which is undefined because
// the object pointed to by the returned
// reference is const, because y is const.
y.baz(); // well-formed, always undefined
}
I refer to the ISO C++03 standard.
const_cast 'this' in const method to assign 'this' to outer variable?
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.
Is const_cast(this) with a write operation undefined behaviour, if the actual object is non-const?
That's not undefined. That's exactly what const_cast
is for. As long as the object itself is non-const
then you can cast it away with const_cast
and do the same things with it as a non-const
pointer.
Do note that const_cast
is usually considered a code smell and might indicate bad design.
As the standard says:
In the body of a non-
static
([class.mfct]) member function, the
keywordthis
is a prvalue whose value is a pointer to the object for
which the function is called. The type of this in a member function of
a classX
isX*
. If the member function is declared const, the type of
this is constX*
, if the member function is declaredvolatile
, the
type of this isvolatile X*
, and if the member function is declared
const volatile
, the type of this isconst volatile X*
.
The type of this
is const X*
in your case even though the object itself is non-const
.
The standard says this about const_cast
:
For two similar types
T1
andT2
, a prvalue of typeT1
may be
explicitly converted to the typeT2
using aconst_cast
. The
result of aconst_cast
refers to the original entity.
So, casting from const X*
to X*
is also legal.
Lastly, it says (albeit in a note):
[ 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 aconst
-qualifier may produce undefined
behavior ([dcl.type.cv]). — end note ]
And [dcl.type.cv]
tells us:
Any attempt to modify ([expr.ass], [expr.post.incr], [expr.pre.incr])
a const object ([basic.type.qualifier]) during its lifetime
([basic.life]) results in undefined behavior.
Luckily, our this
is pointing to a non-const
object, so casting it and then modifying this object through the new non-const
pointer doesn't trigger undefined behaviour.
Sorry Angew.
How do I remove code duplication between similar const and non-const member functions?
Yes, it is possible to avoid the code duplication. You need to use the const member function to have the logic and have the non-const member function call the const member function and re-cast the return value to a non-const reference (or pointer if the functions returns a pointer):
class X
{
std::vector<Z> vecZ;
public:
const Z& z(size_t index) const
{
// same really-really-really long access
// and checking code as in OP
// ...
return vecZ[index];
}
Z& z(size_t index)
{
// One line. One ugly, ugly line - but just one line!
return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
}
#if 0 // A slightly less-ugly version
Z& Z(size_t index)
{
// Two lines -- one cast. This is slightly less ugly but takes an extra line.
const X& constMe = *this;
return const_cast<Z&>( constMe.z(index) );
}
#endif
};
NOTE: It is important that you do NOT put the logic in the non-const function and have the const-function call the non-const function -- it may result in undefined behavior. The reason is that a constant class instance gets cast as a non-constant instance. The non-const member function may accidentally modify the class, which the C++ standard states will result in undefined behavior.
Why can I call a method that changes a member from a const method?
The const
applies to the pointer itself, f
, not to what this pointer is pointing to. The type of f
inside your const
-qualified member function, Test::test()
, is Foo* const
(i.e., const
pointer to Foo
), not const Foo*
(i.e., pointer to const Foo
). That's why you can modify what the pointer is pointing to in spite of the const
qualification of the member function.
Note that the following sample member function, Test::test2()
, does fail to compile since it is const
-qualified and tries to modify the pointer data member, f
:
void Test::test2() const {
f = nullptr; // <-- error
}
Const function calling non const or vice versa (to avoid duplication)?
If you have to make a function that is const-agnostic, and avoids duplication, one neat way to do it is delegating implementation to a template, for example
class Foo {
private:
int my_int;
template <typename ThisPtr>
static auto& get(ThisPtr this_ptr) {
return this_ptr->my_int;
}
public:
int& get() {
return get(this);
}
const int& get() const {
return get(this);
}
};
This way you are free from the fear associated with using const_cast
, mutable
and other stuff that goes into trying to reduce code duplication in cases like this. If you get something wrong, the compiler will let you know.
Return 'this' as non-const from const member function
The reason why this fails is that inside a const
member function, this
is really a const Point*
, not a Point*
. Thus you are trying to initialize a non-const
reference from a const
pointer. It's not that the compiler isn't believing you, you're just asking for two incompatible things at one time.
This is one of the very few valid uses of const_cast
, in my opinion. Normally, using const_cast
is almost always a sign of a design error, or worse a programming error.
Here, the function is really const
and should be const
, but there is no reason why you shouldn't be able to chain something non-const
afterwards, so it's arguably legitimate to do such a thing.
Do note, however, although the function is strictly const
(in respect to the object, not so much in its use of IO functions!), one thing you should consider is that in some (rare) cases, it may result in code that doesn't do what you want. The compiler is allowed to cache the result of a const
function and omit another call to the same const
function (since you promised that it won't change anything). Therefore, it is allowable to optimize some_point.Print().Print();
into some_point.Print()
. This is probably not a problem for you (why would you want to print the same values twice), just something to be generally aware of.
Related Topics
How to Initialize an Array of Struct in C++
How to Output Array of Doubles to Hard Drive
Opencv's Canny Edge Detection in C++
Why Do We Even Need the "Delete[]" Operator
Win32 C/C++ Load Image from Memory Buffer
Element Count of an Array in C++
Boost.Python Call by Reference:Typeerror: No To_Python (By-Value) Converter Found for C++ Type:
How to Control My Pc's Fan Speed Using C++ in Vista
Boost::Asio with Boost::Unique_Future
How Does Sizeof Know the Size of the Operand Array
Memoized, Recursive Factorial Function
How to Read a File at Compile Time
How to Get at the Exception Information When Using Minidumpwritedump Out-Of-Process