17

The following program compiles with both gcc and clang, but is this actually standard C++11 or do both compilers choose to support it for convenience?

struct Foo {
    int i;

    void bar() { std::cout << i << std::endl; }
};

int main() {
    std::function<void(Foo*)> method = &Foo::bar;

    Foo myFoo{4};
    method(&myFoo); // prints 4
}

This is certainly convenient, but I don't understand how/why it works.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • It works as with any class member function pointer, that requires _`this`_ for calling (just hides the ugly stuff). What do you not understand in particular? As is your question looks a bit too broad. – πάντα ῥεῖ Nov 01 '14 at 19:40
  • Yes, that's standard. G++ supports this here in addition though: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html – Deduplicator Nov 01 '14 at 19:43
  • 3
    @πάνταῥεῖ: I think he's curious that this seems to be a place where the standard exposes the implementation detail that `this` is an argument to member functions. – Oliver Charlesworth Nov 01 '14 at 19:44
  • Yeah, what @OliverCharlesworth said. – Barry Nov 01 '14 at 19:55
  • Related: [How do i write a pointer-to-member-function with std::function?](http://stackoverflow.com/q/9281172/335858). There is a good explanation of how this "magic" works in one of the answers. – Sergey Kalinichenko Nov 01 '14 at 19:55
  • @πάνταῥεῖ, I think, that OP does not understand why void (__thiscall Foo::*)(void) converts to void (__cdecl*)(struct Foo *) – grisha Nov 01 '14 at 19:56
  • @user2451677 That one is easy: it doesn't. :) –  Nov 01 '14 at 19:57
  • @hvd: Yes, those calling-conventions are incompatible. GCC allows it as an extension when the calling-conventions are compatible though. – Deduplicator Nov 01 '14 at 21:53

1 Answers1

9

Yes, that is standard. [func.wrap.func.inv] specifies that the operator()(ArgTypes&&... args)
of std::function calls

INVOKE (f, std::forward<ArgTypes>(args)..., R) (20.8.2), where f is the target object (20.8.1) of *this.

(Where R is the specified return type.)

[func.require] defines INVOKE:

Define INVOKE (f, t1, t2, ..., tN) as follows:

  • (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;

  • ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;

  • […]

Note that the trailing R in the call is used for the conversion to R (the return type of the function):

Define INVOKE (f, t1, t2, ..., tN, R) as INVOKE (f, t1, t2, ..., tN) implicitly converted to R.

The first and only argument you give is the pointer to the Foo-object. The call to method thus results in the call (void)((*t1).*f)() which is, when written with your given values, equivalent to
((*(&myFoo)).&Foo::bar)(), which is equivalent to myFoo.bar().

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 4
    A shame they didn't just define a function `std::invoke` while they were at it... – Deduplicator Nov 01 '14 at 19:51
  • @Deduplicator Well, it's more or less trivially implemented - one has to consider the ranking via a priority-based overload resolution mechanism though, there was a bugged implementation in libstdc++ IIRC. – Columbo Nov 01 '14 at 20:00
  • What the what! You can make a function point to member data too? (`std::function data = &Foo::i;`) Man I'm learning all sorts of things today. – Barry Nov 01 '14 at 20:06
  • @Barry Yes, covered in the same quotes. – Columbo Nov 01 '14 at 20:10
  • 1
    @Deduplicator A few years late better than never? – Barry Sep 25 '17 at 18:20