1

Suppose we have the following code:

#include <iostream>

struct A
{
    virtual void f() { 
        std::cout << "A::f()" << std::endl;
    }
};

struct B: A
{
    void f() { 
        std::cout << "B::f()" << std::endl;
    }
};

void to_A(void* voidp) {
    A* aptr = static_cast<A*>(voidp);
    aptr->f();
}

void to_B(void* voidp) {
    B* bptr2 = static_cast<B*>(voidp);
    bptr2->f();
}

int main() {
    B* bptr = new B;
    void* voidp = bptr; 
    to_A(voidp); // prints B::f()
    to_B(voidp); // prints B::f()
}

is this code guaranteed to always work as in the code comments or is it UB? AFAIK it should be ok, but I'd like to be reassured.

EDIT
Ok, so it seems there's a consensus that this code is UB, as one can only cast to the exact type. So, what if the main() changes to:

int main() {
    B* bptr = new B;
    to_A(static_cast<A*>(bptr)); // still prints B::f()
    to_B(bptr); // still prints B::f()
}

is it still UB?

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
Simone
  • 11,655
  • 1
  • 30
  • 43
  • possible duplicate of [When should static_cast, dynamic_cast and reinterpret_cast be used?](http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-and-reinterpret-cast-be-used) – Martin York Dec 30 '10 at 09:30
  • The code in EDIT does not compile... Undefined variable voidp. – Tomek Dec 30 '10 at 17:21

4 Answers4

3

In this specific case it works, but there are other cases in which it doesn't work.

The problem is the place where you cast the B-pointer to void-pointer to A-pointer. In this case the pointers will all have the same value, but in the following conditions this isn't true anymore:

  • the base class has no virtual methods (therefore, no vptr), but the subclass has virtual methods (I once encountered such a bug in my company's software)
  • the subclass uses multiple inheritance

The only safe way is to cast it exactly back to the same type as where you came from. So if you cast a B-pointer to void-pointer, cast it back to a B-pointer, not to a pointer to another class, even if they belong to the same inheritance tree.

Patrick
  • 23,217
  • 12
  • 67
  • 130
  • Casting from void* to X* is only guaranteed to work if the pointer was originally an X* to start with. Any other types there is no guarantee (ie the OP is getting lucky that his class's are simple). – Martin York Dec 30 '10 at 09:26
  • do you mean that it work by chance or that it is *guaranteed* to work? – Simone Dec 30 '10 at 09:54
  • @Simone: It works by chance. There is no language guarantee as to what behaviour a `B*` converted to `void*` then `static_cast` to `A*` will result in. – CB Bailey Dec 30 '10 at 10:12
3

Your first code example invokes undefined behaviour.

You can use a static_cast to reverse a standard conversion of pointer to object type to pointer to void but the result is only guaranteed if the value of the pointer to void being converted back to the original object type is the result of the standard conversion of a pointer to the original type to pointer to void.

Your second code example is OK because you only reverse conversions from pointer-to-type to pointer-to-void by casting back to the original type that the conversion was made from. This is guaranteed in 5.2.9 [expr.static.cast] of the standard (C++03).

... A value of type pointer to object converted to “pointer to cv void” and back to the original pointer type will have its original value.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 1
    @Simone: I'm not totally sure. He says "In this specific case it works" but that may just be interpreting the results that you are seeing, there's no guarantee. He then says "there are other cases in which it doesn't work" which is also correct. He the does some analysis based on some implementation assumptions as to why it might work. I prefer to be explicit: it's undefined behaviour. – CB Bailey Dec 30 '10 at 09:44
  • I think this answer is correct, and so should be accepted. :-) – Nawaz Dec 30 '10 at 10:13
  • @Simone: The code post **Edit:** has defined behaviour even if it looks very error prone. Please don't materially change SO questions after answers have been posted. It automatically invalidates the answers that already exist. – CB Bailey Dec 30 '10 at 10:33
  • well, now everything matches, sorry for the mess. – Simone Dec 30 '10 at 10:53
1

This is actually quite a common thing to try to do, especially in functions that require a C callback, so one has to be careful. A typical C API callback looks like:

void pfnCallback( void * );

In your C++ code you decide to use a base class to always handle this particular callback and we call it

struct BaseCallback
{
 virtual ~BaseCallback();
 virtual call();
};

We also write a single function that we always use for this C API:

void OurCallback( void * var )
{
   BaseCallback * ourBase = static_cast< BaseCallback * >)(var);
   ourBase->call();
}

As we are going to be casting always from void* to BaseCallback * we must be careful when we first supply the parameters the other way that we are always going to cast the BaseCallback* to void*.

CashCow
  • 30,981
  • 5
  • 61
  • 92
-2

I think, you're not using casting properly. I would recommend you to read first two responses from here:

When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used?

Let's first know when which cast should be used!


EDIT:

As for your question,

is this code guaranteed to always work as in the code comments or is it UB? AFAIK it should be ok, but I'd like to be reassured.

If you want to pass pointer of ANY type to your to_A() function, then I think static_cast is not the one you should use. You should use reinterpret_cast. But if you want to pass only pointer to B ( or any derived class of A) to to_A() function, then it would always work. If by "work" you meant whether it will return non-null value.

Similar argument for to_B() function. I think, passing A* to to_B() might fail. That means, it could simply return NULL, Or if not null, A::f() will be called from to_B().

See this interesting output: http://www.ideone.com/2FZzw (it prints A::f(), not B::f() !!!)

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Why should it print B::f() if you are instantiating an A struct? http://www.ideone.com/nw1WI – Simone Dec 30 '10 at 09:15
  • @Simone : that is what is confusing. What do you mean by "work"? It depends on your intention? Do you it to convert to other type? Is that what you meant by "work"? – Nawaz Dec 30 '10 at 09:16
  • @Nawaz I mean to print "B::f()" in both function call as described in the comments. – Simone Dec 30 '10 at 09:17
  • @Simone: in that case, I think it will always work, as long as you pass pointer to B. But the name of your first function is misleading, as it doesn't really convert B to A. – Nawaz Dec 30 '10 at 09:19
  • @Nawaz you're right but I didn't really spent much time thinking about names, since the example was different from production code. – Simone Dec 30 '10 at 09:20
  • @Simone: that's why I didn't understand what you meant by "working code". I thought you're trying to convert one to another which is not possible using static_cast here. – Nawaz Dec 30 '10 at 09:22
  • @Simone : by the way, I liked the idea of playing with such code, casting and all. It often helps you understand how different castings work. Thanks a lot for creating the topic. +1. – Nawaz Dec 30 '10 at 09:24
  • well, the real code involves also crossing a C code section, that's why a void* is needed. – Simone Dec 30 '10 at 09:35
  • Using `reinterpret_cast` is definitely not going to "solve" anything that `static_cast` isn't solving in this case. Both versions would still result in undefined behaviour. – CB Bailey Dec 30 '10 at 09:52