Search code examples
c++c++11stdstd-functionmember-functions

Using std::function for member functions


My question is about using std::function to class methods. Suppose I have the following class hierarchy:

class Foo {
public:
    virtual void print() {
        cout << "In Foo::print()" << endl;
    }

    virtual void print(int) {
        cout << "In Foo::print(int)" << endl;
    }
};

class Bar : public Foo {
public:
    virtual void print() override {
        cout << "In Bar::print()" << endl;
    }

    virtual void print(int) override {
        cout << "In Bar::print(int)" << endl;
    }
}

Now there is another function which is supposed to dynamically call one of the two class methods depends on its input:

void call(Foo* foo, void (Foo::*func)(void)) {
    (foo->*func)();
}

Foo* foo = new Foo();
Bar* bar = new Bar();
call(foo, &Foo::print);
call(bar, &Foo::print);

When I compile the above code snippet using g++/clang++, it works as expected, where the output is:

In Foo::print()
In Bar::print()

My questions are then:

  1. since there are two functions (overloaded) with the same name: print, when I pass the address of class function: &Foo::print, how did the compiler know that I am actually calling Foo::print(void) but not Foo::print(int)?

  2. is there another way that I can generalize the code above such that the second parameter of void call(Foo*, xxx) can be passed using both Foo::print(void) and Foo::print(int)

  3. is there anyway to achieve this feature using new feature in C++11 std::function ? I understand that in order to use std::function with a non-static class method, I have to use std::bind to bind each class method with a specific class object, but that would be too inefficient for me because I have many class objects to be bound.


Solution

  • Since there are two functions (overloaded) with the same name: print, when I pass the address of class function: &Foo::print, how did the compiler knows that I am actually calling Foo::print(void) but not Foo::print(int)?

    This is allowed because of [over.over]/p1:

    A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set.

    The compiler can use the target type of the parameter-type-list to determine which function from the overload set the pointer-to-member refers:

    A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. A function template name is considered to name a set of overloaded functions in such contexts. The function selected is the one whose type is identical to the function type of the target type required in the context. [ Note: .. ] The target can be

         — an object or reference being initialized (8.5, 8.5.3, 8.5.4),
         — the left side of an assignment (5.18),
         — a parameter of a function (5.2.2),
         — [..]

    The name Foo:print represents an overload set which the compiler looks through to find a match. The target type Foo::print(void) is present in the overload set, so the compiler resolves the name to that overload.

    Is there another way that I can generalize the code above such that the second parameter of void call(Foo*, xxx) can be passed using both Foo::print(void) and Foo::print(int)

    There isn't a general way to do it with the name itself. The name has to be resolved to an overload. Instead, try changing the code to accept a function object like a lambda:

    template<class Callable>
    void call(Foo* foo, Callable&& callback) {
        callback(foo);
    }
    
    int main()
    {
        call(foo, [] (Foo* f) { f->print(); f->print(1); });
    }