why std::function that store a call to member function can have two different callable type

Viewed 133
struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};

int main ()
{
    function<void(const Foo*, int)> func = &Foo::print_add;
    function<void(const Foo&, int)> func2 = &Foo::print_add;
    Foo f(1), f2(2);
    func(&f, 2);
    func2(f2, 3);
    return 0;
}

why func and func2 both can call member function Foo::print_add correctly? they have different callable type as template argument. From what I know, ‘this’ pointer is passed as a hidden argument to all nonstatic member function calls, the callable type of func2 recieve const Foo& as argument which doesn't match the type of this pointer. why the code above doesn't generate a compiler error?

2 Answers

std::function stores and can "invoke any CopyConstructible Callable target".

The Callable named requirement is defined as follows:

 if f is a pointer to member function of class T: 

    If std::is_base_of<T, std::remove_reference_t<decltype(t1)>>::value
    is true, then INVOKE(f, t1, t2, ..., tN) is equivalent to
    (t1.*f)(t2, ..., tN)

Or, in other words: if after removing any reference qualifier on the first parameter the end result is the member function's class (or a subclass), the member function gets invoked for the instance of the class.

otherwise, if t1 does not satisfy the previous items,
then INVOKE(f, t1, t2, ..., tN) is equivalent to
((*t1).*f)(t2, ..., tN).

Otherwise: cross your fingers, hope that the first parameter is a pointer to an instance of the member function's class, and invoke it via the given pointer (hoping against hope that it's a pointer, or something that pretends that it's a pointer).

Final results: either one is equivalent to the other.

why func and func2 both can call member function Foo::print_add correctly?

They can because standard says that they can.

From what I know, ‘this’ pointer is passed as a hidden argument

From the language perspective, there is no "hidden pointer passed". A function is called "on" an object and this is simply a keyword that produces pointer to that object. Considering the notation not_a_pointer.member_function(other_arguments) there appears to be no pointer being passed syntactically.

Underneath the languge, from the implementation perspective (i.e. where "hidden" things are passed), there isn't a difference in the code that references and pointers produce. They're just memory addresses.


As an aside: It would be much more convenient if this produced a reference rather than a pointer. To my understanding, member functions and this were added to the language before references were added, so at that point reference wasn't an option. And afterwards, such change to the language would be backward incompatible.

Related