3

I define 2 classes

class BaseA {
public:
    virtual void methodA() = 0;
};
class BaseB {
public:
    virtual void methodB(int val) = 0;
};

Child inherits 2 Base Class

class Child : public BaseA, public BaseB {
public:
    void methodA() override {
        printf("Child A\n");
    }
    void methodB(int val) override {
        printf("Child B %d\n", val);
    }
};

Then I write following code.

void callBaseB(void *p) {
    BaseB *b = (BaseB *) p;
    b->methodB(0);
}
int main() {
    auto child = new Child;
    callBaseB(child);
    return 0;
}

console print Child A
Why this happened? Why not call method B?

(This is what happend when a Java engineer try to write C++ code)

desperateLord
  • 303
  • 2
  • 9
  • 5
    You can't cast a `void *` to a `BaseB *` like that. You have completely flummoxed the compiler. – Paul Sanders Feb 27 '22 at 12:19
  • I suggest you rename the question to `Why the wrong override method is called` – Sergey Kolesnik Feb 27 '22 at 12:21
  • 1
    What is the purpose of `void*` here instead of using `BaseB*`? `void*` is one of those C++ constructs that should only be used if there is no other way to solve the problem and only if being very careful not to make a mistake. – user17732522 Feb 27 '22 at 12:22
  • 1
    You must cast `void*` back to original type and nothing else. Related/duplicate: [C++ typecast: cast a pointer from void pointer to class pointer](https://stackoverflow.com/questions/10072004/c-typecast-cast-a-pointer-from-void-pointer-to-class-pointer), [Casting to void* and Back to Original_Data_Type*](https://stackoverflow.com/questions/12275321/casting-to-void-and-back-to-original-data-type) – Yksisarvinen Feb 27 '22 at 12:23
  • @PaulSanders I think I confused compiler too. But in some senario I have to cast void pointer. Is there any way to do so? Extra informaton? – desperateLord Feb 27 '22 at 12:24
  • 1
    @desperateLord The caller has to first cast to the base class, before passing the pointer to the function. – user17732522 Feb 27 '22 at 12:25
  • @Yksisarvinen I guess it is not exactly so strict: `there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.` form cppreference. `BaseA`* in this case is pointer-interconvertible – Sergey Kolesnik Feb 27 '22 at 12:26
  • @SergeyKolesnik They are not pointer-interconvertible. That applies only to standard-layout classes. – user17732522 Feb 27 '22 at 12:27
  • @user17732522 it doesn't seem to "apply to only standard layout" since there is another part that explicitly mentions stabdard layout: `one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object, or` https://en.cppreference.com/w/cpp/language/static_cast – Sergey Kolesnik Feb 27 '22 at 12:28
  • You can only cast from void * to Child*. After that you could cast to BaseB*. – QuentinUK Feb 27 '22 at 12:29
  • 1
    @SergeyKolesnik "one is a standard-layout class object" is supposed to apply to both parts of the "or". And all classes involved are not standard-layout because they have virtual functions. – user17732522 Feb 27 '22 at 12:29
  • @user17732522 _The caller has to first cast to the base class, before passing the pointer to the function._ Good call. That lets the compiler perform the necessary pointer adjustment. – Paul Sanders Feb 27 '22 at 12:32
  • @user17732522 so why does ` auto baseA = static_cast(child);` compiles then? – Sergey Kolesnik Feb 27 '22 at 12:32
  • 1
    @SergeyKolesnik The pointer-interconvertibility requirement applies to casts from `void*`, or equivalently `reinterpret_cast`s. Your cast is a `static_cast` from `Child*` to `BaseA*`, for which it doesn't matter. Your cast is well-formed and has well-defined behavior. – user17732522 Feb 27 '22 at 12:34
  • @user17732522 what I mean, wouldn't `Child*` -> `void*` -> `BaseA*` be valid in this case? If we are invoking `methodA` – Sergey Kolesnik Feb 27 '22 at 12:37
  • @SergeyKolesnik No, it wouldn't be. That is exactly the danger of `void*` and `reinterpret_cast`/C-style casts. It doesn't work within non-standard-layout class hierarchies (and is quite limited for standard-layout classes as well). You should only use `static_cast` or implicit casts without going through `void*` for that. – user17732522 Feb 27 '22 at 12:38
  • @user17732522 well it is dangerous if you pass something that doesn't have `BaseA*` as a first base class. I'm not implying that this code is alright. I'm talking why wouldn't it be working, if `BaseA*` and `Child*` are interchangable, so `Child*` -> `void*` -> `BaseA*` would also be valid. https://godbolt.org/z/5q1raETrq This code compiles and works as expected with optimization on for GCC. So since I don't see where it is explicitly stated to be a UB (other then your interpretation), why *would it be UB*? Once again: *I do agree that void* is very bad as a C++ API for a function* – Sergey Kolesnik Feb 27 '22 at 12:44
  • @SergeyKolesnik In your link the result of `static_cast(p)` is a pointer of type `BaseA*` pointing to the object created with `new child`. It is not a pointer to the `BaseA` subobject, because that subobject is not pointer-interconvertible with the parent object (see previous comments). Then you dereference this pointer which has UB, because the pointer of type `BaseA` doesn't actually point to a `BaseA` object. This is undefined behavior by omission of any defined behavior. If you want detailed arguments about this point, you can probably find some questions about it here on SO. – user17732522 Feb 27 '22 at 12:50
  • @SergeyKolesnik Of course, if the subobject happens to have the same address as the parent object, then it is not unlikely that the code will still happen to work in practice, but 1. it is not guaranteed that the first base class subobject of a non-standard-layout class shares the same address with the parent object (although the used ABI may say something about that) and 2. even if it does, it would still be undefined behavior according to the standard for reasons given above. – user17732522 Feb 27 '22 at 12:52
  • In modern C++, use of `void*` is rarely the best way to do anything, except for compatibility with an old or C-language API. – aschepler Feb 27 '22 at 13:18

2 Answers2

5

You should just do this: void callBaseB(BaseB *p) {p->methodB(0);}.

If you want to keep void *p as a parameter, you need to cast it to exactly Child * first. Either:

BaseB *b = (Child *)p;
b->methodB(0);

Or:

Child *b = (Child *)p;
b->methodB(0);

Alternatively, cast to BaseB * before converting to void *. Then casting from void * back to Base * will work.


What happens here is that the BaseB subobject is at non-zero offset inside of Child.

When you convert Child * to BaseB * (either explicitly with a cast, or implicitly, either by assigning pointers or by calling a method of BaseB on the pointer), the compiler automatically offsets the pointer by the required amount, to point to the BaseB subobject.

The offset is determined entirely at compile-time (unless virtual inheritance is involved), based on the two types.

When you obscure the source type using void *, the compiler has no way to determine the correct offset, and doesn't even try to apply any offset, so you get weird behavior, because the resulting BaseB * doesn't point to a BaseB instance, but rather to some junk inside of Child.

Even with virtual inheritance, despite the offset being determined at runtime, the calculation depends on the source pointer type being known.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • I try using your method and print the pointer. Yes, the pointer skewed about 8 when I pass to BaseB* type parameter. It makes a lot sense now. Thx. – desperateLord Feb 27 '22 at 12:36
  • Hmmm. Please see my comment to @solvedgames answer starting '...' – Paul Sanders Feb 27 '22 at 12:38
  • @PaulSanders *"Now you are assuming that the caller always passes a `Child *`"* Yes, and there's no workaround for this, except for getting rid of `void *` parameter. It's up to the caller to adjust the pointer, and the caller needs to know the target type to do it. – HolyBlackCat Feb 27 '22 at 13:11
  • @HolyBlackCat Sure, but that target type might as well be a `Base *`, since that is what `callBaseB` actually wants. Forcing it to assume that the caller is, in actuality, passing a `Child *` is unnecessarily inflexible. – Paul Sanders Feb 27 '22 at 14:46
0

Casting a void* to a BaseB* cannot be done (as @PaulSanders said), but you can definitely cast a Child* to a BaseB* as follows:

void callBaseB(Child* p) 
{
    BaseB* b = p;
    b->methodB(0);
}

The above code should successfully call methodB().

But if you really need to use void*, you can so something like this:

void callBaseB(void* p) 
{
    Child* b = (Child*)p;
    b->methodB(0);
}
The Coding Fox
  • 1,488
  • 1
  • 4
  • 18
  • Yes, It can. But my senario I save pointer to Java Long. So I just have to cast void pointer. – desperateLord Feb 27 '22 at 12:30
  • I've edited the answer to use `void*`. – The Coding Fox Feb 27 '22 at 12:34
  • You don't need a cast at all here. You can just do `BaseB* b = p;`. And it doesn't solve the OP's stated goal of passing a `void *` into `callBaseB` for whatever reason he has to do that. – Paul Sanders Feb 27 '22 at 12:34
  • I have edited the answer to fulfill that requirement of the OP. – The Coding Fox Feb 27 '22 at 12:35
  • ... and if anything, your second proposal makes things even worse. Now you are assuming that the caller always passes a `Child *` to `callBaseB`, and I don't think the OP has given you any reason to do that. – Paul Sanders Feb 27 '22 at 12:36
  • Then is there any way better than this to fulfill the OP's requirements? And I also think the OP's satisfied with this. (From @HolyBlackCat's answer's comments). – The Coding Fox Feb 27 '22 at 12:39
  • _is there any way better than this to fulfill the OP's requirements?_ Yes, have the caller cast his `Child *` parameter to `Base *`. This lets the compiler apply the necessary pointer adjustment at the call site and also allows a `Base *` to be passed if desired. Of course, this relies on the caller getting it right, but then that's the cost of using a `void *` in the first place. – Paul Sanders Feb 27 '22 at 12:43
  • @PaulSanders maybe I can just save 2 pointer. one for BaseA, one for BaseB. Or some extra info to correctly cast to child. – desperateLord Feb 27 '22 at 12:43
  • No, just include the necessary cast at the call site and cast to `BaseB *` in `callBaseB`. – Paul Sanders Feb 27 '22 at 12:44