3

I have the following c++11 code, which works, while I would have expected it to crash or even not to compile. Retrieving a pointer to a pure virtual member function should return a null or invalid pointer, or should be blocked by the compiler. I would like to understand why it works.

I know there are other (better) ways to code this, this is a purely theoritical question for understanding what the syntax does.

#include <iostream>
#include <functional>

class Abstract
{
public:
    void foo()
    {
        auto func = std::bind(&Abstract::virtualFoo, this);
        func();
    }

protected:
    virtual void virtualFoo() = 0;
};


class Derived1 : public Abstract
{
private:
    void virtualFoo() override
    {
        std::cout << "I am Derived1\n";
    }
};

class Derived2 : public Abstract
{
private:
    void virtualFoo() override
    {
        std::cout << "I am Derived2\n";
    }
};


int main(int argc, char *argv[])
{
    Abstract * a1 = new Derived1;
    Abstract * a2 = new Derived2;

    a1->foo();
    a2->foo();

    return 0;
}

The intent is quite clear, in the base class function foo() I want to get a pointer on the derived virtual functions.

However, to my understanding, it should not work, and should not even compile with a pure virtual function. With a non pure virtual function, it should execute the base class function. But, I was very surprised to see it compiles, and produces the intended output : it prints "I am Derived1" then "I am Derived2"

How can &Abstract::virtualFoo return a valid pointer, without even knowing the pointer to the actual object, mandatory for accessing a vtable???

Online C++ link : https://onlinegdb.com/SJfku8rvV

To me, a valid syntax should be:

        auto func = std::bind(&this->virtualFoo, this);

As dereferencing this should actually access the vtable and return a function pointer. But the c++11 doesn't think this way.

galinette
  • 8,896
  • 2
  • 36
  • 87
  • There *is* an entry in the vtable for `virtualFoo`, but its value is zero in the ABC. – Botje Mar 12 '19 at 16:06
  • 1
    if instead of using the function pointer and then calling `func();` you'd simply call `virtualFoo();` you would get the same output, I dont really understand why you think this should not work – 463035818_is_not_an_ai Mar 12 '19 at 16:06
  • While `std::bind` can be useful in some situations, most use-cases should instead use lambdas. Including this case shown here in your code. – Some programmer dude Mar 12 '19 at 16:08
  • @Botje : yes, but in this case why isn't this passing a null pointer to bind? – galinette Mar 12 '19 at 16:13
  • @Someprogrammerdude : this is purely a MCVE, I did it to avoid calling virtualFoo() directly to illustrate the question by retrieving a pointer explicitely. Of course I know lambdas. – galinette Mar 12 '19 at 16:15
  • What you've discovered here is that pointer-to-members [aren't simple memory addresses](https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713). They're compiler-defined objects that are set up to Do The Right Thing™ – Miles Budnek Mar 12 '19 at 16:21
  • @MilesBudnek : do they have the same sizeof() than standard pointers or may they be larger? In other terms, are they not guaranteed to be castable to and from uintptr_t ? – galinette Mar 12 '19 at 16:34
  • @galinette They can be any size, so no, pointer-to-member types cannot be reliably cast to `uintptr_t` or `void*`. The blog post I linked talks about how MSVC implements them for multiple inheritance, and the size of a pointer-to-member varies depending on the class whose member is being pointed to. In that case it can be either the size of a single pointer or the size of a pointer plus a `size_t`. – Miles Budnek Mar 12 '19 at 16:41

1 Answers1

5

How can &Abstract::virtualFoo return a valid pointer, without even knowing the pointer to the actual object, mandatory for accessing a vtable???

You've declared the function to be virtual. The compiler knows that the function is virtual. The standard requires that calling through the member function pointer does a virtual dispatch.

The compiler stores the necessary information into the member function pointer to make that happen. Note that a member function pointer is not necessarily merely a pointer to a single address. It can contain more than that.

The exact way in which the compiler achieves this is implementation defined.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thanks. This would mean for instance that it's not possible to cast them to plain function pointers with "this" added as first argument, and also that they cannot be cast to uintptr_t. In other words these are not pointer types but more complex structures. – galinette Mar 12 '19 at 16:36
  • @galinette yes, [`[basic.compound]`](http://eel.is/c++draft/basic.compound) distinguishes "*pointers* to cv void or objects or functions" from "*pointers to non-static class members*" see also [`[dcl.ptr]`](http://eel.is/c++draft/dcl.ptr) and [`[dcl.mptr]`](http://eel.is/c++draft/dcl.mptr) – Caleth Mar 12 '19 at 16:50