0

I understand why you cannot simply cast a derived class member function pointer to base class member function pointer as explained here.

But, given this snippet:

struct base
{
    virtual void foo() = 0;
};

struct derived : base
{
    void foo() override {};
};

struct invoker
{
    typedef void(base::*target)();

    invoker(base* b, target t)
    {
        (b->*t)();
    }
};

template<typename B, typename D>
void (B::*cast(void (D::*method)()))()
{
    return static_cast<void(B::*)()>(method);
}

derived d;
invoker bad(&d, &derived::foo); //C2664
invoker good(&d, cast<base>(&derived::foo));

I wanted to ask is it possible to decorate the base function signature so that compiler understands it is a pure virtual method and and it will be implemented somewhere across the hierarchy (otherwise I could not construct an object of type B)? I understand why I can't do this with normal functions, but IMHO in case of a pure virtual function the compiler has a guarantee it will be implemented (in case it was not done I would get an error about the class B not about the cast).

Community
  • 1
  • 1
Rudolfs Bundulis
  • 11,636
  • 6
  • 33
  • 71
  • 1
    It is unclear what kind of decoration you are asking for. If you were a supreme head of the C++ design committee, what would you do? Don't worry about consistency or correctness, just show what you want to add to C++. If you just want to tell the compiler that `derived::foo` exists in `base`, you can simply write `&base::foo`. – n. m. could be an AI Jul 28 '16 at 13:39
  • @n.m. I want to avoid making the explicit cast:D But it was more of a "is there some way that I am not aware of" type of a question. – Rudolfs Bundulis Jul 28 '16 at 13:44
  • 1
    No, don't make a cast. Just start with `&base::foo`. What does `&derived::foo` do what `&base::foo` cannot? – n. m. could be an AI Jul 28 '16 at 14:34
  • @n.m - you actually just blew my mind. I tried that and got into the derived function. Can you explain where does the vtable magic take place then? I am passing a pointer to `base::foo` which is pure virtual. I am invoking it via a function pointer (so does that involve vtable lookup or no?). Thanks:) – Rudolfs Bundulis Jul 28 '16 at 14:48
  • 1
    pointer to member functions respect virtuality just like with the normal member functin syntax. Normally this is implemented by storing a vtable offset somewhere in the ptr-to-mem. – n. m. could be an AI Jul 28 '16 at 15:00
  • @n.m - yeah I just checked under the debugger. I was not aware of this, I expected that it was just a pointer to the physical address of the function that gets invoked with the value of instance pointer whatever it may be:) – Rudolfs Bundulis Jul 28 '16 at 15:08
  • @n.m maybe you want to post an answer saying that I don't need to cast and vtable lookup will work just fine? :) Since I wasn't aware of this maybe someone else will find this useful. – Rudolfs Bundulis Jul 28 '16 at 15:20

3 Answers3

2

There's no need to manipulate the type of &derived::foo. One can just use &base::foo instead.

Pointers to member functions respect virtuality. This call

base* pBase = new derived;
auto pFoo = &base::foo;
(pBase->*pFoo)();

will actually call derived::foo, exactly like a simple call pBase->foo() would.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
0

Even if there's a guarantee that it's implemented, it might use additional data members declared only in derived. A possible solution is to use function pointers and pass this as the first parameter (this also shows you why you cannot do it via virtual).

lorro
  • 10,687
  • 23
  • 36
  • Since I am passing `base*` I would end up in the correct override due to runtime polymorphism so I cannot access anything that is not there. Or did I misunderstand you? – Rudolfs Bundulis Jul 28 '16 at 13:32
  • Add an `int i;` to derived and do `i = 0xDEADBEEF;` to `derived::foo()` to see what I mean. It works the other way around: you might pass `Base` function pointer where a `derived` one is expected. Note that *you* know that you're passing `derived*`, but C++ compiler might not know it.. – lorro Jul 28 '16 at 13:49
  • No I can't since I can't construct an instance of `Base` since it has a pure virtual member. I am not talking about the general case, but about one where the target of the cast is a pure virtual member of the `Base`. – Rudolfs Bundulis Jul 28 '16 at 13:52
  • Ok, have the above and have `struct derived2 : base` that implements this method as well. Now, you might end up passing `derived2*` as a `base*` and a `derived` member function pointer. – lorro Jul 28 '16 at 14:02
  • When I wrote the question I had an asumption that vtable lookup somehow magically happens with function pointers as well, but there is no way it can happen so the question is ill formed, sorry:) – Rudolfs Bundulis Jul 28 '16 at 14:07
  • No, it's a completely valid question. Actually, adding functionality w/o data to classes should be added to C++ as this could allow for a lot of things: `std::vector` could work (I mean, using `(&(v[i]))->fn()`), this would allow for chameleon classes (that change runtime type), simplify state machines and the above specialization :). Current workaround is a static member or non-member function (and a pointer member in base to it) that takes `base*` as the first param. Not that it's beautiful, but at least it works. – lorro Jul 28 '16 at 14:26
  • @RudolfsBundulis There is nothing "magic" about vtable access. – curiousguy Aug 01 '16 at 17:54
0

Consider the following diamond hierarchy:

struct base {
virtual void foo() = 0;
};

struct D1 : public virtual base {
virtual void foo() override;
};

struct D2 : public virtual base {
virtual void foo() override;
};

struct Derived : public virtual D1, D2 {
virtual void foo() final;
};

Now consider a scenario where a upcast is allowed from Derived::* to base::* . Which function should be invoked? The compiler loses information about which of D1::foo, D2::foo or Derived::foo you wish to call since that information has been cast away. To avoid this sort of ambiguity such an upcast is disallowed.

  • I guess my point of failure was that the vtable lookup happens on function pointers as well (and in that case the correct method would actually be called), but I guess it does not happen. – Rudolfs Bundulis Jul 28 '16 at 13:56
  • "_Which function should be invoked?_" `foo()`, I guess – curiousguy Aug 01 '16 at 08:57