1

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.

2
  • You said in prototype call(Foo* foo, void (Foo::*func)(void)), thus you'll get (void) overload Commented May 8, 2015 at 19:04
  • @SeverinPappadeux that's right. But when I give &Foo::print when passing the argument, how did the compiler decide which to pass? Commented May 8, 2015 at 19:18

2 Answers 2

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 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); }); } 
Sign up to request clarification or add additional context in comments.

1 Comment

That's awesome! The explanation of overloaded set rather than a single name makes perfect sense to me and the last lambda trick with move semantics is both elegant and efficient. Thanks a lot!
1

First, std::bind is (almost) entirely outmoded by C++11 lambdas. Don't use std::bind if you can help it. One of these is much clearer than the others, using your example code:

const auto lambda = [=] { foo->print(); }; // Clear! const auto binderv = std::bind( static_cast<void(Foo::*)()>( &Foo::print ), foo ); // Gets the void version const auto binderi = std::bind( static_cast<void(Foo::*)(int)>( &Foo::print ), foo, std::placeholders::_1 ); // Gets the int version //const auto binderv2 = std::bind( &Foo::print, foo ); // Error! Can't tell which Foo::print() //const auto binderi2 = std::bind( &Foo::print, foo, std::placeholders::_1 ); // Error! Can't tell which Foo::print() lambda(); // prints "void" binderv(); // prints "void" binderi(1); // prints "int" 

Second, how does the compiler know which overloaded function to call? The same way it would if you were using non-member functions:

#include <iostream> void call( void (*fn)() ) { fn(); } void print() { std::cout << "void\n"; } void print(int) { std::cout << "int\n"; } int main() { call( &print ); // prints "void" } 

Only one of those overloaded functions fits the called function's prototype, so the compiler knows. In the case of std::bind above, it can't quite tell, but you can force it with a cast, as I did.

Lambdas or std::function can wrap either of the member functions, but note that you can't overload a function on different std::function signatures. See here.


Update:

The right way to handle your question #3 -- to have one function call functions with drastically different signatures like yours -- is to use some intermediary like a functor (lambda, std::function, std::bind, hand-rolled functor) to erase the differences.

std::function<void()> objects that have the same signature, regardless of what the real functions you're calling have as their signature. std::function is more expensive (in terms of storing and calling) than a lambda but it has the advantage of having a typename that you can use if you need to store it in a container or something. Lambdas can sometimes be inlined away by the compiler if you play your cards right, so efficiency may still favor lambdas.

2 Comments

Thanks for the explanation! const auto lambda is good but it won't work for me because I have a lot of such objects like foo, say, > 1000. I am assuming using such techniques I have to create a separate lambda for each such object, which would be very inefficient. Thanks!
Yeah right, I agree. Anyway, thanks for the solution!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.