tags:

views:

130

answers:

4
+1  Q: 

Casting "Stylee"

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

A: 

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?

graham.reeds
@graham I cite myself for you:"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?"
There is nothing we can do
@A-ha: Yes NEVER use a C cast in C++ code. Anything that can be done by a C cast can be done by a combination of C++ casts. The benefit is that they are easier to spot and show up the vulnerabilities in your code much easier. They do NOT add any run-time cost over C.
Martin York
+1  A: 

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.

FredOverflow
@FredOverflow Ahem.. those are not my words, those are words of mister Herb Sutter whose style of explaining things irritates me like hell.
There is nothing we can do
@A-ha: I strongly suspect this is an error on Sutter's part.
FredOverflow
@FredOverflow @A-ha: I think it is more A-ha's interpretation of H.Sutter that is in error here.
Martin York
@Martin York at what point did I make any interpretation? And if my interpretation of his words is incorrect I'll be glad to hear yours.
There is nothing we can do
@Martin York and funny how you "think" that my interpretation is incorrect but do not give yours.
There is nothing we can do
@A-ha. No need for an interpretation by me. As it is yet unclear what you mean. But I think H.Sutter is well know for his exactness that I doubt there is anything wrong with his statement whatever it is (as you do not provide a reference to where the quote is coming from (a link or a book page number would be nice)).
Martin York
@Martin York and what according to you is this:(from first sentence from this Q)"From the book Exceptional C++ Solution to ch. 44 ", if this isn't a reference... Did you actually read this question? Sometimes I really wonder... And no you are not right saying that there is no need for your interpretation. If you saying that I've misinterpreted something you should explain why. Simply saying it's wrong because - doesn't work.
There is nothing we can do
@Martin: Everybody makes mistakes, even Herb Sutter. Have you seen the [Exceptional C++ Errata List](http://www.gotw.ca/publications/xc++-errata.htm)?
FredOverflow
@Martin I wonder if you still doubt there is anything wrong with his (Herb Sutters') statement whatever it is? Wow, Herb Sutter said something so it must be right! Wow. Free thinking!
There is nothing we can do
@A-ha: No such book (an exact title or ISBN No would be better). Do you want to type out the full quote so we can explain it to you sentence by sentence in its correct context. Note: Just because H.Sutter says it does not mean it is correct, but given your relative experience in comparison to his, the preponderance of proof is on your side (there is only finite time in my life so I will go with the accepted expert (until proved wrong) over somebody who is still working out how to use cast correctly).
Martin York
@Martin and just because "there is only finite time in your life so I will go with the accepted expert" it just one of the most stupid things to say. According to you you do not think. If expert said it must be true (according to you) and you not going to waste your time to think about it. Congratulations.
There is nothing we can do
@Martin first learn how to read, then learn how to use web. What do you mean by No such book (an exact title ... would be better? And what for God's sake Exceptional C++ is? It is the the exact title you fucking MORON! Type in Google "Exceptionall C++". And just because I'm just learning it doesn't mean that I do not see errors. Think for God's sake!
There is nothing we can do
@Martin and if you would understand what you've read you would know that I do not ask how to use cast operators, I ask something else because one of "your" experts made fool of himself and published something which isn't true. Another graham.reeds...
There is nothing we can do
Martin York
@Martin you've typed into Amazon and I've typed into Amazon and what? How many books is there with title Exceptional C++? One. Just because there are other books with similar titles it doesn't mean that I was referring to them does it? Did I said in my question "In a book with title similar to"? Think for God's sake and if you trying to do something (look for something for example) use the simplest most effective way. Google is well known for that. Amazon isn't.
There is nothing we can do
@Martin I have to agree with you that as a programmer exactness is required. That's why you should look for book with title which is exactly "Exceptional C++" and not for books with similiar title don't you think so?
There is nothing we can do
@Martin @Martin in chronological order your sense of exactness: "@FredOverflow @A-ha: I think it is more A-ha's interpretation of H.Sutter that is in error here. Martin York"So you without even checking you think that it is my misinterpretation. So you do not EXACTLY know what's going on but you are sure that's my fault. Bravo!
There is nothing we can do
@MartinLet's continue:"@A-ha: No such book (an exact title or ISBN No would be better). Martin York" This comment proves that you do not EXACTLY understand what you're reading because this book is mentioned in the first sentence of my question.
There is nothing we can do
@Martin But that's not the end, then you start to insult in a subtle way:"@A-ha: ...over somebody who is still working out how to use cast correctly. Martin York"Again, if you would understand EXACTLY what I'm asking for you would know that I do not ask how to use cast operators. But that's your exactness. You just read without thorough understanding what you are reading.
There is nothing we can do
@Martin But that's not the end of your "exactness". This one is the best in my opinion:"@A-ha: I typed it into Amazon: There are three similar books with nearly that title. Martin York"How difficult it is to find on the net book with EXACTLY this title "Exceptional C++" and not being distracted by other books with similar titles? And did I mention in my question book with title similar to "Exceptional C++"? Buhahaha...
There is nothing we can do
@Martin you're saying: "I typed it into Amazon: There are three similar books with nearly that title". Wrong there are two books with nearly that title and ONE with EXACTLY this title. So what's wrong with your exactness and with your logic? And why if you have three books one of which has exactly this same title you still saying "No such book". What logic is that?
There is nothing we can do
@Martin and as for Sutter - yes he did make a fool of himself and proved that he doesn't EXACTLY understand what he is trying to explain.
There is nothing we can do
@A-ha: Use the correct tool for the job. Amazon is designed to look-up books (As a result I think it is the correct tool).
Martin York
@A-Ha: You typed "Exceptional C++ Solution to ch. 44". I am sorry but all three books contain: "Exceptional C++ and the word Solution and none are an exact match (So you can see my confusion). That is why I asked for a link or an ISBN which you could have easily provided, or you could have easily typed in the exact quote. But instead went on a trollish tirade rather than try and make a constructive effort to clear up the situation. Therefore you can see my reluctance to accept your version. Anyway it was an enjoyable evening I hope you have resolved your misunderstanding of the written word.
Martin York
Martin York
@Martin no problem I will update my original Q and after that I hope that you will admit that Herb Sutter doesn't really EXACTLY understand (in this particular case) what he is talking about. Because if he would understand he wouldn't say it would he? Hope to see reply from you after reading my update.
There is nothing we can do
+4  A: 

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.

Mike Seymour
@Mike thanks for your answer. But wouldn't there be performance penalty while using two casts instead of one? (I'm talking of course about situation when this is done many times)
There is nothing we can do
No, the compiler still has to implement the same logic however you split it up. Casts are not really a runtime thing, they don't make any performance difference whatsoever. User-defined operators are of course notwithstanding.
DeadMG
@A-ha: in this case, no; both `reinterpret_cast` and `const_cast` happen at compile time. `dynamic_cast` has a cost, but isn't equivalent to a C cast. `static_cast` can sometimes have a runtime cost of adjusting the pointer value, or calling a user-defined constructor/conversion operator, but the same adjustment must be made by the equivalent C cast anyway. In any event, a decent compiler will generate identical code for two equivalent conversions.
Mike Seymour
@A-ha: `const_cast` and `reinterpret_cast` are pure compile-time mechanisms that don't have any effect on runtime performance.
FredOverflow
+2  A: 

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.

Dennis Zickefoose
@Dennis I'm going to edit my original Q and I will put whole code from this book and you can see it for yourself that rest of the code is irrelevant to this example.
There is nothing we can do