is it legal to cast between pointers on classes that have common ancestor? Does the compiler notice such hierarchy and makes sure its safe (call 1) ?
Or does the user have to go through the hierarchy manually for it to be always safe (call 2) ?
say we have
class A{};
class B:A{};
class C:A
{
public:
int SomeFunc(){return 3;}
};
int _tmain(int argc, _TCHAR* argv[])
{
B* b = (B*)((A*)new C()); // this is done manually, i believe it is safe
((C*)b)->SomeFunc(); // is this safe? this is the cast in question
return ((C*)((A*)b))->SomeFunc(); // this is done manually, i believe it is safe
}
edit: Made this code compilable and runnable
edit2: Added more comments
B* b = (B*)((A*)new C()); // this is done manually, i believe it is safe
This is not safe.
Casts of the form (T) expr are, roughly speaking, converted into either static_cast or reinterpret_cast. [expr.cast]/4:
The conversions performed by
a const_cast (5.2.11),
a static_cast (5.2.9),
a static_cast followed by a const_cast,
a reinterpret_cast (5.2.10), or
a reinterpret_cast followed by a const_cast,
can be performed using the cast notation of explicit type conversion.
The same semantic restrictions and behaviors apply […] If a
conversion can be interpreted in more than one of the ways listed
above, the interpretation that appears first in the list is used, even
if a cast resulting from that interpretation is ill-formed.
You can ignore const_cast here as no qualification conversions are done in your code.
static_cast suffices in both casts, the first one, (A*), and the second one, (B*).
The first one is just fine. Upcasting is never an issue.
The second one induces undefined behavior. [expr.static.cast]/11:
A prvalue of type “pointer to cv1 B,” where B is a class type,
can be converted to a prvalue of type “pointer to cv2 D”, where
D is a class derived (Clause 10) from B, if a valid standard
conversion from “pointer to D” to “pointer to B” exists (4.10),
cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base
class of a virtual base class of D. […] If the prvalue of type
“pointer to cv1 B” points to a B that is actually a subobject of an
object of type D, the resulting pointer points to the enclosing
object of type D. Otherwise, the result of the cast is undefined.
Note also that just because the static_cast triggers UB that doesn't mean it isn't selected (and replaced by reinterpret_cast).
The second and third casts base on the first one (which causes undefined behavior), thus talking about their validity is pointless.
Unless you really really know what your doing, don't do that.
The casts are legal, but using them on anything but the correct class results in undefined behaviour, any use of b without further casts results in UB, which might work, do nothing or start WWIII.
The casts simply tells the compiler that is should consider the variable to be of another type (unless it is multiple inheritance), but as soon as the cast variable is used it must actual be legal to use it in the way the code does, using B's function table is no good if the object is a C or vice versa. As this is undefined behaviour the compiler might emit whatever code it feels is right.
Example
class AA { };
class BB { };
class CC : public AA, public BB { };
int main () {
CC cc; // address is 0x22aa6f
BB* bb = &cc; // bb now is 0x22aa6f
cout << &cc << "," << bb << "\n";
return EXIT_SUCCESS;
}
Gives
0x22aa6f,0x22aa6f
Example with multiple inheritance
class AX{ int a = 1; };
class BX{ int b = 2; };
class CX:AX,BX {
public:
int c = 3;
int SomeFunc(){cout << "SomeFunc " << c << " "; return c;}
};
int cast() {
CX* c;
BX* b = (BX*)((AX*)(c = new CX())); // this is done manually, i believe it is safe
cout << "c=" << c << ", b=" << b << ", cx=" << ((CX*)b) << ", ca=" << ((CX*)((AX*)b)) << endl;
((CX*)b)->SomeFunc(); // is this safe? this is the cast in question
return ((CX*)((AX*)b))->SomeFunc(); // this is done manually, i believe it is safe
}
int main () {
return cast();
}
output
c=0x60003ac70, b=0x60003ac70, cx=0x60003ac6c, ca=0x60003ac70
SomeFunc 2 SomeFunc 3
c is the real address of the new
b is the cast to AX first which is c then to BX (which doesn't make any sense for AX) but b is just set to the same address as c
cx is b reinterpreted as CX, multi-inheritance kicks in and really change the address as if b was the 2nd class in the inheritance.
ca is the correct reinterpretation of b through AX and then CX.
The 2 calls to SomeFunc works despite the wrong address
the function call is found through the current type which is CX due to the last cast.
the wrong addresses are passed as this pointer
because this is not used it works, so I had to add some use.
now we have entered undefined behaviour due to the cast (BX*)((AX*)c which makes the cast (CX*)b do the wrong thing.
To check if it is safe you need to use dynamic_cast.
int main() {
A* AP = new C();
C* CP = dynamic_cast<C*>(A);
if (CP != nullptr)
CP->SomeFunc();
return EXIT_SUCCESS;
}
To check whether a cast is meaningful or not just use dynamic_cast. dynamic_cast will cast properly if cast is safe OR returns NULL (in case of pointers, for references it throws bad_cast exception ) if its not able to cast to target type.
For your question just think about whether this cast is meaningful. You are casting B class to C where these classes have no knowledge of each other. So, surely this cast would fail.
You are doing:-
B* b = (B*)(new C());
This would fail(means won't even compile ) if given to dynamic_cast since classes involved are not polymorphic. Even if you make class B polymorphic cast would fail. Leave further casting.
One more thing you can cross cast using dynamic_cast safely assuming classes are polymorphic and cast is safe. For e.g:-
class A;
Class B;
Class C : public A, public B
A *a = new C;
You can cast this to sibling:-
B *b = dynamic_cast<B*> (a);
Your casts are both legal and correct but very dangerous. You should use reinterpret_cast<> to tag them in your code.
You can always cast any address of any type A to any other address of any type B and get your first address back. This is essentially what you've done:
A *pa = &some_a;
B *pb = reinterpret_cast<B *>(pa);
pa = reinterpret_cast<A *>(pb);
and then dereference pa. This example works but it is so easy to make a mistake...
Related
I was reading about strict aliasing, but its still kinda foggy and I am never sure where is the line of defined / undefined behaviour. The most detailed post i found concentrates on C. So it would be nice if you could tell me if this is allowed and what has changed since C++98/11/...
#include <iostream>
#include <cstring>
template <typename T> T transform(T t);
struct my_buffer {
char data[128];
unsigned pos;
my_buffer() : pos(0) {}
void rewind() { pos = 0; }
template <typename T> void push_via_pointer_cast(const T& t) {
*reinterpret_cast<T*>(&data[pos]) = transform(t);
pos += sizeof(T);
}
template <typename T> void pop_via_pointer_cast(T& t) {
t = transform( *reinterpret_cast<T*>(&data[pos]) );
pos += sizeof(T);
}
};
// actually do some real transformation here (and actually also needs an inverse)
// ie this restricts allowed types for T
template<> int transform<int>(int x) { return x; }
template<> double transform<double>(double x) { return x; }
int main() {
my_buffer b;
b.push_via_pointer_cast(1);
b.push_via_pointer_cast(2.0);
b.rewind();
int x;
double y;
b.pop_via_pointer_cast(x);
b.pop_via_pointer_cast(y);
std::cout << x << " " << y << '\n';
}
Please dont pay too much attention to a possible out-of-bounds access and the fact that maybe there is no need to write something like that. I know that char* is allowed to point to anything, but I also have a T* that points to a char*. And maybe there is something else I am missing.
Here is a complete example also including push/pop via memcpy, which afaik isn't affected by strict aliasing.
TL;DR: Does the above code exhibit undefined behaviour (neglecting a out-of-bound acces for the moment), if yes, why? Did anything change with C++11 or one of the newer standards?
Aliasing is a situation when two entities refer to the same object. It may be either references or pointers.
int x;
int* p = &x;
int& r = x;
// aliases: x, r и *p refer to same object.
It's important for compiler to expect that if a value was written using one name it would be accessible through another.
int foo(int* a, int* b) {
*a = 0;
*b = 1;
return *a;
// *a might be 0, might be 1, if b points at same object.
// Compiler can't short-circuit this to "return 0;"
}
Now if pointers are of unrelated types, there is no reason for compiler to expect that they point at same address. This is the simplest UB:
int foo( float *f, int *i ) {
*i = 1;
*f = 0.f;
return *i;
}
int main() {
int a = 0;
std::cout << a << std::endl;
int x = foo(reinterpret_cast<float*>(&a), &a);
std::cout << a << "\n";
std::cout << x << "\n"; // Surprise?
}
// Output 0 0 0 or 0 0 1 , depending on optimization.
Simply put, strict aliasing means that compiler expects names of unrelated types refer to object of different type, thus located in separate storage units. Because addresses used to access those storage units are de-facto same, result of accessing stored value is undefined and usually depends on optimization flags.
memcpy() circumvents that by taking the address, by pointer to char, and makes copy of data stored, within code of library function.
Strict aliasing applies to union members, which described separately, but reason is same: writing to one member of union doesn't guarantee the values of other members to change. That doesn't apply to shared fields in beginning of struct stored within union. Thus, type punning by union is prohibited. (Most compilers do not honor this for historical reasons and convenience of maintaining legacy code.)
From 2017 Standard: 6.10 Lvalues and rvalues
8 If a program attempts to access the stored value of an object
through a glvalue of other than one of the following types the
behavior is undefined
(8.1) — the dynamic type of the object,
(8.2) — a cv-qualified version of the dynamic type of the object,
(8.3) — a type similar (as defined in 7.5) to the dynamic type of the
object,
(8.4) — a type that is the signed or unsigned type corresponding to
the dynamic type of the object,
(8.5) — a type that is the signed or unsigned type corresponding to a
cv-qualified version of the dynamic type of the object,
(8.6) — an aggregate or union type that includes one of the
aforementioned types among its elements or nonstatic data members
(including, recursively, an element or non-static data member of a
subaggregate or contained union),
(8.7) — a type that is a (possibly cv-qualified) base class type of
the dynamic type of the object,
(8.8) — a char, unsigned char, or std::byte type.
In 7.5
1 A cv-decomposition of a type T is a sequence of cvi and Pi such that T is “cv0 P0 cv1 P1 · · · cvn−1 Pn−1 cvn U” for n > 0, where each
cvi is a set of cv-qualifiers (6.9.3), and each Pi is “pointer to”
(11.3.1), “pointer to member of class Ci of type” (11.3.3), “array of
Ni”, or “array of unknown bound of” (11.3.4). If Pi designates an
array, the cv-qualifiers cvi+1 on the element type are also taken as
the cv-qualifiers cvi of the array. [ Example: The type denoted by the
type-id const int ** has two cv-decompositions, taking U as “int” and
as “pointer to const int”. —end example ] The n-tuple of cv-qualifiers
after the first one in the longest cv-decomposition of T, that is,
cv1, cv2, . . . , cvn, is called the cv-qualification signature of T.
2 Two types T1 and T2 are similar if they have cv-decompositions with
the same n such that corresponding Pi components are the same and the
types denoted by U are the same.
Outcome is: while you can reinterpret_cast the pointer to a different, unrelated and not similar type, you can't use that pointer to access stored value:
char* pc = new char[100]{1,2,3,4,5,6,7,8,9,10}; // Note, initialized.
int* pi = reinterpret_cast<int*>(pc); // no problem.
int i = *pi; // UB
char* pc2 = reinterpret_cast<char*>(pi+2); // *(pi+2) would be UB
char c = *pc2; // no problem, unless increment didn't put us beyond array bound.
// c equals to 9
'reinterpret_cast' doesn't create objects. To dereference a pointer at a non-existing object is Undefined Behaviour, so you can't use dereferenced result of cast for writing if class it points to wasn't trivial.
I know that char* is allowed to point to anything, but I also have a T* that points to a char*.
Right, and that is a problem. While the pointer cast itself has defined behaviour, using it to access a non-existent object of type T is not.
Unlike C, C++ does not allow impromptu creation of objects*. You cannot simply assign to some memory location as type T and have an object of that type be created, you need an object of that type to be there already. This requires placement new. Previous standards were ambiguous on it, but currently, per [intro.object]:
1 [...] An object is created by a definition (6.1), by a new-expression (8.3.4), when implicitly changing the active member of a union (12.3), or when a temporary object is created (7.4, 15.2). [...]
Since you are not doing any of these things, no object is created.
Furthermore, C++ does not implicitly consider pointers to different object at the same address as equivalent. Your &data[pos] computes a pointer to a char object. Casting it to T* does not make it point to any T object residing at that address, and dereferencing that pointer has undefined behaviour. C++17 adds std::launder, which is a way to let the compiler know that you want to access a different object at that address than what you have a pointer to.
When you modify your code to use placement new and std::launder, and ensure you have no misaligned accesses (I presume you left that out for brevity), your code will have defined behaviour.
* There is discussion on allowing this in a future version of C++.
Short answer:
You may not do this: *reinterpret_cast<T*>(&data[pos]) = until there has been an object of type T constructed at the pointed-to address. Which you can accomplish by placement new.
Even then, you might need to use std::launder as for C++17 and later, since you access the created object (of type T) through a pointer &data[pos] of type char*.
"Direct" reinterpret_cast is allowed only in some special cases, e.g., when T is std::byte, char, or unsigned char.
Before C++17 I would use the memcpy-based solution. Compiler will likely optimize away any unnecessary copies.
Does the C++ standard define a particular behaviour if you make an old C-style cast from type A to type B where type A cannot be cast to type B and vice versa?
Would there be a known visible behavior that can be assumed to be symptom of a illegal cast in runtime using this?
Only one of the four C++-style casts determines the validity of the cast at runtime, namely dynamic_cast.
A C-style cast corresponds to a combination of the other three casts (static_cast, reinterpret_cast, const_cast). The validity of those casts is determined at compile-time, or if it cannot be determined at compile-time then the cast is assumed to be valid. A C-style cast never acts like dynamic_cast.
So a C-style cast that "fails" at runtime, i.e. breaks the validity assumption, causes undefined behavior. So:
Does the C++ standard define a particular behaviour if you make an old C-style cast from type A to type B where type A cannot be cast to type B and vice versa?
No.
Would there be a known visible behavior that can be assumed to be symptom of a illegal cast in runtime using this?
No.
Compiler will catch some of them, but not all. Here is an example of a disastrous cast:
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A(std::string s, int x) : m_s(s), m_x(x) {}
void print() { cout << "A::print(): str = " << m_s << ", x = " << m_x << endl; }
private:
std::string m_s;
int m_x;
};
class B {
public:
B(int x) : m_x(x) {}
void print() { m_x++; cout << "B::print(): x = " << m_x << endl; }
private:
int m_x;
};
int main(int argc, char **argv) {
A *a = new A("abc", 1);
a->print();
B *b = (B*)a;
b->print();
a->print();
return 0;
}
Result on gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609:
A::print(): str = abc, x = 1
B::print(): x = 24828977
A::print(): str = bc, x = 1
Yes, we called B's methods on A's data, which means that our C-style cast worked as reinterpret_cast. reinterpret_cast just takes a memory region and allows you to treat is as something different. No seatbelts here.
On the other hand, if you try to compile something like this
A a;
B b;
a = (A)b;
return 0;
it will result in static_cast and compile-time error. So C-style casting result depends on context.
This is NIGHTMARE when dealing with templates and auto.
To avoid this problem, use static_cast (for compile-time checks) and dynamic_cast (for runtime checks) or reinterpret_cast (for pointers and POD), clearly stating your intent.
Please read my question before linking parashift, I can google search, this is slightly different case.
This isn't allowed
Child **cc;
Base ** bb = cc;
Because you could do
*bb = new OtherChild;
But if we have
Child **cc;
const Base *const *const bb = cc;
I don't think all those const are necessary for my example, but just to be sure..
I think the minimum which should work is
Base *const *bb = cc;
Then you can't do this
*bb = new OtherChild;
So it should be safe. But why isn't it allowed?
You're confusing two cases:
The addition of const
Upcasts
While formally (in computer science theory) both of these deal with subclassing, the reality is that the C++ rules for these are different, because the representation of const T and T are guaranteed to be the same, while the representations of Base* and Derived* often differ by an offset (but may be radically different when virtual inheritance is involved).
In 3.9.3, the Standard declares that
The cv-qualified or cv-unqualified versions of a type are distinct types; however, they shall have the same representation and alignment requirements
Given:
struct Base {};
struct Derived : Base {};
Derived* pd = nullptr;
Base* pb = pd;
const can indeed by added in the way you suggest.
Base const* const* const cpcpcb = &pb;
Base* const* pcpb = &pb; // legal, pointer can't be changed
Base const* * ppcb = &pb; // illegal, one could try to rebind the pointer
// to a truly const object, then
// use pb to mutate the const object
But there is no is-a relationship between Derived* and Base*. A conversion exists, but the Derived* variable does not necessarily contain the address of a Base object (the Base subobject within the Derived object may have a different address). And therefore both the line you're complaining about, and the line your question assumed was valid, are illegal:
Base const* const* const cpcpcd = &pd; // error, there's no address of a Base
// to be found in pd
Base* const* pcpd = &pd; // error: again, there's no address of a Base
// stored in pd
Formally, the Standard describes this in 4.10:
A prvalue of type "pointer to cv D”, where D is a class type, can be converted to a prvalue of type "pointer to cv B", where B is a base class of D. If B is an inaccessible or ambiguous base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.
The result of the conversion is a prvalue, it doesn't have an address, and you can't create a pointer to it.
This is a constraint imposed by the C++ language, because implicit conversion of pointer types is based on inheritance rules (with the exception of implicit conversion to void *).
That is, given the following program, the assignment is only allowed if T is a base of U:
T const *x;
U *y;
x = y;
However, in your case:
typedef Base *T;
typedef Child *U;
The relationship between Base and Child does not transfer to their pointer types. So, there is no inheritance relationship between "pointer to Base" and "pointer to Child" to allow the direct assignment you desire.
I think that in your equation you are expecting const to play a bigger role than what it really has.
const is a qualifier, and simply put, its role is to regulate the way you access a type or a variable.
The main point is if the language itself, without even considering what is const or not, already regulates a given behaviour, your specific case is not that different from the one reported by that FAQ.
You are in a really simple situation, and you should probably think in much simpler terms.
For static_cast, Is it true that, unless there exist a built-in type conversion function, you cannot use static_cast to perform conversion. But you can do a reinterpret_cast for a type, considering the return type is valid.
int main()
{
WORD word;
HWND hwnd = static_cast<HWND>(word); // error
HWND hwnd = reinterpret_cast<HWND>(word); // ok, considering a valid handle is returned.
}
Do the explicit type conversions done with static_cast require a conversion function unlike reinterpret_cast?
reinterpret_cast just allows you to convert completely unrelated types. It just treats the chunk of memory as another type. So it is very unsafe to use it, since it just doesn't give you any compile or runtime errors but just causes (usually) crash
static_cast provides compile time check of validity of an cast. If an type cannot be treated as another type then static_cast gives you an compile time error when attempting an cast.
It does implicit conversions between types (such as int to float, or pointer to void*), and it can also call explicit conversion functions (or implicit ones).
So you can say that it can do the implicit casts for which there is an implicit conversion inbuilt function present. It is usually considered as replacement for c-style casting if that is the confusion.
The C++ casts make most sense when casting pointers and references.
Concrete examples
void foo (Base & b) {
if (b .is_a_Foo ())
static_cast <Foo &> (b) .bar ();
else
b .do_default_bar ();
dynamic_cast <Baz &> (b) .something (); // throws if invalid conversion
}
char data [4];
* reinterpret_cast <float *> (data) = 1.23;
The Windows API is a horrible hack from top to bottom -- in your example, reinterpret_cast is faithful to the original intent (and highlights it for the world to admire) and it basically means "throw away the type system and use the raw bits: trust me".
Basically static_cast allocates memory for compatible class size of destination type and fills it with what is possible, but without any checking that new object is complete. Let me give you an example:
class A {
public:
int a;
};
class B : public A {
public:
int c;
int b;
};
int main()
{
A *a = new A;
a->a = 5;
B *b = new B;
b->a = 6;
b->b = 7;
b->c = 8;
B* bb = static_cast<B*>(a);
A* aa = static_cast<A*>(b);
cout << bb->a << endl; // 5
cout << bb->b << endl; // scrap value from memory
// member b was not initialized, because it was not found in A
cout << aa->a << endl; // 6
return 0;
}
In your example static cast is invalid, because hwnd is void * and word is unsigned short. For c++ casts any type can be considered as a class;
reinterpret_cast works always. It is just a binary copy
From the book Exceptional C++ Solution to ch. 44 I've learned that they are situations when none of the new cast styles would work properly. I always thought that they (those 4 new casts) cover every possible situation and there is no need for "old" style cast anymore, but it appears to be not true. So my question is:
Are those new casts cover all possible situations so there is no need to ever use c-style cast or:
There are situation in which only the old cast works properly?
Thanks.
That's appropriate fragment from this book:
"
class A { public: virtual ~A(); /*...*/ };
A::~A() { }
class B : private virtual A { /*...*/ };
class C : public A { /*...*/ };
class D : public B, public C { /*...*/ };
A a1; B b1; C c1; D d1;
const A a2;
const A& ra1 = a1;
const A& ra2 = a2;
char c;
void f()
{
A* pa; B* pb; C* pc;
pa = (A*)&ra1;
pa = (A*)&a2;<<----------This is the cast I'm interested in
//This cannot be expressed as a new-style cast. The closest candidate is const_cast,
//but because a2 is a const object, the results of using the pointer are undefined.
//Not my words those are words of Herb Sutter. (whose style of writing irritates me to bits)
pb = (B*)&c1;
pc = (C*)&d1;
}
"
EDITED
Chapter 44 from Exceptional C++:
"Item 44. Casts
Difficulty: 6
How well do you know C++'s casts? Using them well can greatly improve the reliability of your code.
The new-style casts in standard C++ offer more power and safety than the old-style (C-style) casts. How well do you know them? The rest of this problem uses the following classes and global variables:
class A { public: virtual ~A(); /*...*/ };
A::~A() { }
class B : private virtual A { /*...*/ };
class C : public A { /*...*/ };
class D : public B, public C { /*...*/ };
A a1; B b1; C c1; D d1;
const A a2;
const A& ra1 = a1;
const A& ra2 = a2;
char c;
This Item presents four questions.
Which of the following new-style casts are not equivalent to a C-style cast?
const_cast
dynamic_cast
reinterpret_cast
static_cast
For each of the following C-style casts, write the equivalent new-style cast. Which are incorrect if not written as a new-style cast?
void f()
{
A* pa; B* pb; C* pc;
pa = (A*)&ra1;
pa = (A*)&a2;
pb = (B*)&c1;
pc = (C*)&d1;
}
Critique each of the following C++ casts for style and correctness.
void g()
{
unsigned char* puc = static_cast<unsigned char*>(&c);
signed char* psc = static_cast<signed char*>(&c);
void* pv = static_cast<void*>(&b1);
B* pb1 = static_cast<B*>(pv);
B* pb2 = static_cast<B*>(&b1);
A* pa1 = const_cast<A*>(&ra1);
A* pa2 = const_cast<A*>(&ra2);
B* pb3 = dynamic_cast<B*>(&c1);
A* pa3 = dynamic_cast<A*>(&b1);
B* pb4 = static_cast<B*>(&d1);
D* pd = static_cast<D*>(pb4);
pa1 = dynamic_cast<A*>(pb2);
pa1 = dynamic_cast<A*>(pb4);
C* pc1 = dynamic_cast<C*>(pb4);
C& rc1 = dynamic_cast<C&>(*pb2);
}
Why is it typically unuseful to const_cast from non-const to const? Demonstrate a valid example in which it can be useful to const_cast from non-const to const."
Solution to chapter 44
Solution
Let's answer the questions one by one.
Which of the following new-style casts are not equivalent to a C-style cast?
Only dynamic_cast is not equivalent to a C-style cast. All other new-style casts have old-style equivalents.
Guideline
Prefer new-style casts.
For each of the following C-style casts, write the equivalent new-style cast. Which are incorrect if not written as a new-style cast?
void f()
{
A* pa; B* pb; C* pc;
pa = (A*)&ra1;
Use const_cast instead:
pa = const_cast<A*>(&ra1);
pa = (A*)&a2;
This cannot be expressed as a new-style cast. The closest candidate is const_cast, but because a2 is a const object, the results of using the pointer are undefined.
pb = (B*)&c1;
Use reinterpret_cast instead:
pb = reinterpret_cast<B*>(&c1);
pc = (C*)&d1;
The above cast is wrong in C. In C++, no cast is required:
pc = &d1;
}
Critique each of the following C++ casts for style and correctness.
First, a general note: All of the following dynamic_casts would be errors if the classes involved did not have virtual functions. Fortunately, A does provide a virtual function, making all the dynamic_casts legal.
void g()
{
unsigned char* puc = static_cast<unsigned char*>(&c);
signed char* psc = static_cast<signed char*>(&c);
Error: We must use reinterpret_cast for both cases. This might surprise you at first, but the reason is that char, signed char, and unsigned char are three distinct types. Even though there are implicit conversions between them, they are unrelated, so pointers to them are unrelated.
void* pv = static_cast<void*> (&b1);
B* pb1 = static_cast<B*>(pv);
These are both fine, but the first is unnecessary, because there is already an implicit conversion from a data pointer to a void*.
B* pb2 = static_cast<B*> (&b1);
This is fine, but unnecessary, since the argument is already a B*.
A* pa1 = const_cast<A*>(&ra1);
This is legal, but casting away const (or volatile) is usually indicative of poor style. Most of the cases in which you legitimately would want to remove the const-ness of a pointer or reference are related to class members and covered by the mutable keyword. See Item 43 for further discussion of const-correctness.
Guideline
Avoid casting away const. Use mutable instead.
A* pa2 = const_cast<A*>(&ra2);
Error: This will produce undefined behavior if the pointer is used to write on the object, because a2 really is a const object. To see why this is a legitimate problem, consider that a compiler is allowed to see that a2 is created as a const object and use that information to store it in read-only memory as an optimization. Casting away const on such an object is obviously dangerous.
Guideline
Avoid casting away const.
B* pb3 = dynamic_cast<B*>(&c1);
Potential error (if you try to use pb3): Because c1 IS-NOT-A B (because C is not publicly derived from B—in fact, it is not derived from B at all), this will set pb3 to null. The only legal cast would be a reinterpret_cast, and using that is almost always evil.
A* pa3 = dynamic_cast<A*>(&b1);
Probable error: Because b1 IS-NOT-AN A (because B is not publicly derived from A; its derivation is private), this is illegal unless g() is a friend of B.
B* pb4 = static_cast<B*>(&d1);
This is fine, but unnecessary because derived-to-public-base pointer conversions can be done implicitly.
D* pd = static_cast<D*>(pb4);
This is fine, which may surprise you if you expected this to require a dynamic_cast. The reason is that downcasts can be static when the target is known, but beware: You are telling the compiler that you know for a fact that what is being pointed to really is of that type. If you are wrong, then the cast cannot inform you of the problem (as could dynamic_cast, which would return a null pointer if the cast failed) and, at best, you will get spurious run-time errors and/or program crashes.
Guideline
Avoid downcasts.
pa1 = dynamic_cast<A*>(pb2);
pa1 = dynamic_cast<A*>(pb4);
These two look very similar. Both attempt to use dynamic_cast to convert a B* into an A*. However, the first is an error, while the second is not.
Here's the reason: As noted above, you cannot use dynamic_cast to cast a pointer to what really is a B object (and here pb2 points to the object b1) into an A object, because B inherits privately, not publicly, from A. However, the second cast succeeds because pb4 points to the object d1, and D does have A as an indirect public base class (through C), and dynamic_cast is able to cast across the inheritance hierarchy using the path B* D* C* A*.
C* pc1 = dynamic_cast<C*>(pb4);
This, too, is fine for the same reason as the last: dynamic_cast can navigate the inheritance hierarchy and perform cross-casts, so this is legal and will succeed.
C& rc1 = dynamic_cast<C&>(*pb2);
}
Finally, an "exceptional" error: Because *pb2 isn't really a C, dynamic_cast will throw a bad_cast exception to signal failure. Why? Well, dynamic_cast can and does return null if a pointer cast fails, but since there's no such thing as a null reference, it can't return a null reference if a reference cast fails. There's no way to signal such a failure to the client code besides throwing an exception, so that's what the standard bad_cast exception class is for.
Why is it normally unuseful to const_cast from non-const to const?
The first three questions included no examples of using const_cast to add const, for example, to convert a pointer to non-const to a pointer to const. After all, explicitly adding const is usually redundant—for example, it's already legal to assign a pointer to non-const to a pointer to const. Normally, we only need const_cast to do the reverse.
And the last part of the question: Demonstrate a valid example where it can be useful to const_cast from non-const to const.
There is at least one case in which you could usefully const_cast from non-const to const—to call a specific overloaded function or a specific version of a template. For example:
void f( T& );
void f( const T& );
template<class T> void g( T& t )
{
f( t ); // calls f(T&)
f( const_cast<const T&>(t) ); // calls f(const T&)
}
Of course, in the case of choosing a specific version of a template, it's usually just easier to name it explicitly instead of forcing the right deduction. For example, to call the right version of a templated function h(), writing "h( t )" is preferable to writing "h( const_cast(t) )".
In that situation, const_cast will have exactly the same effect as a C cast. Both will give a non-const pointer to a constant object, and in both cases trying to modify the object will give undefined behaviour.
Any conversion can be made using some combination of C++ casts, but there are some cases where a C cast can make a conversion that no single C++ cast can. For example, reinterpret_cast can't remove const or volatile qualifications, and const_cast can't convert between two unrelated pointer types; but a C cast can do both at once:
class A;
class B;
A const* a = 0;
B* b;
b = reinterpret_cast<B*>(a); // fail: can't remove const
b = const_cast<B*>(a); // fail: can't convert between pointer types
b = reinterpret_cast<B*>(const_cast<A*>(a)); // OK
b = (B*)a; // OK
I would still prefer to see the the two casts in this case, at the cost of extra typing; it makes it clear that something freaky is going down, and uses a syntax that can be searched for. In my opinion, C casts should never be used for anything.
In C++, the old-style casts are defined in terms of the new-style casts.
5.4:
4 Any type conversion not mentioned
below and not explicitly defined by
the user (12.3) is ill-formed.
5 The conversions performed by
— a const_cast (5.2.11),
— a static_cast (5.2.9),
— a static_cast followed by a
const_cast, — a reinterpret_cast
(5.2.10), or — a reinterpret_cast
followed by a const_cast, can be
performed using the cast notation of
explicit type conversion.
The example you provided is covered quite cleanly by the first bullet. Your comment at the end of the example is only half right. You can read the result, but you can not write to it. This is the same whether you use const_cast or not. The underlying object does not lose its const-ness just because you cast it away.
A few clauses later, a few situations in which a C-style cast behaves differently from a regular static_cast are listed. But they have to do with casting along inheritances in which the base class is inaccessible. The virtual in your example suggests that maybe there was some inheritance in the book's actual code; perhaps that is what he was trying to get at, and you misunderstood?
For completeness:
7 In addition to those conversions, the
following static_cast and
reinterpret_cast operations
(optionally followed by a const_cast
operation) may be performed using the
cast notation of explicit type
conversion, even if the base class
type is not accessible:
— a pointer to
an object of derived class type or an
lvalue of derived class type may be
explicitly converted to a pointer or
reference to an unambiguous base class
type, respectively;
— a pointer to
member of derived class type may be
explicitly converted to a pointer to
member of an unambiguous non-virtual
base class type;
— a pointer to an
object of non-virtual base class type,
an lvalue of non-virtual base class
type, or a pointer to member of non-virtual base class type may be explicitly converted to a pointer, a reference, or a
pointer to member of a derived class type, respectively.
As an example of what that last clause is talking about, here is something only possible with a C-style cast.
class Base { };
class Derived : Base { };
Derived d;
Base* pb;
pb = static_cast<Base*>(&d); //inaccessible base
pb = (Base*)(&d); //just fine
However, I am finding it hard to imagine a situation where this would not be a bad idea. For practical purposes, just assume C-style casts don't exist.
The closest candidate is const_cast, but because a2 is a const object, the results of using the pointer are undefined.
Just to be clear, the C-style cast (A*)&a2 also yields undefined behavior. So const_cast is not "the closest candidate", it is the equivalent.
All that seems to prove is that everything has an edge case. I've never come across that situation in the real world.
By the way, did you have a question?