Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::function fails to distinguish overloaded functions

I am trying to understand why std::function is not able to distinguish between overloaded functions.

#include <functional>  void add(int,int){}  class A {};  void add (A, A){}  int main(){         std::function <void(int, int)> func = add; } 

In the code shown above, function<void(int, int)> can match only one of these functions and yet it fails. Why is this so? I know I can work around this by using a lambda or a function pointer to the actual function and then storing the function pointer in function. But why does this fail? Isn't the context clear on which function I want to be chosen? Please help me understand why this fails as I am not able to understand why template matching fails in this case.

The compiler errors that I get on clang for this are as follows:

test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to       'std::function<void (int, int)>'         std::function <void(int, int)> func = add;                                        ^      ~~~ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note:        candidate constructor not viable: no overload of 'add' matching       'std::__1::nullptr_t' for 1st argument     _LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {}                               ^ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note:        candidate constructor not viable: no overload of 'add' matching 'const       std::__1::function<void (int, int)> &' for 1st argument     function(const function&);     ^ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note:        candidate template ignored: couldn't infer template argument '_Fp'       function(_Fp,       ^ 1 error generated. 

EDIT - In addition to MSalters' answer, I did some searching on this forum and found the exact reason why this fails. I got the answer from Nawaz's reply in this post.

I have copy pasted from his answer here:

    int test(const std::string&) {         return 0;     }      int test(const std::string*) {         return 0;     }      typedef int (*funtype)(const std::string&);      funtype fun = test; //no cast required now!     std::function<int(const std::string&)> func = fun; //no cast! 

So why std::function<int(const std::string&)> does not work the way funtype fun = test works above?

Well the answer is, because std::function can be initialized with any object, as its constructor is templatized which is independent of the template argument you passed to std::function.

like image 781
MS Srikkanth Avatar asked May 22 '15 09:05

MS Srikkanth


People also ask

Which function we Cannot use in function overloading?

Function declarations that differ only by its return type cannot be overloaded with function overloading process. Member function declarations with the same parameters or the same name types cannot be overloaded if any one of them is declared as a static member function.

How are overloaded functions differentiated?

Overloaded functions differentiate between argument types that take different initializers. Therefore, an argument of a given type and a reference to that type are considered the same for the purposes of overloading. They're considered the same because they take the same initializers.

What are the rules for defining overloaded function?

To overload functions, they must have a different set of arguments, i.e. they must either have arguments of different data types or have a different number of arguments in the function definition. Functions with different return types and identical arguments can not be overloaded.

How are function calls matched with overloaded functions?

When a function call is made to an overloaded function, the compiler steps through a sequence of rules to determine which (if any) of the overloaded functions is the best match. At each step, the compiler applies a bunch of different type conversions to the argument(s) in the function call.


1 Answers

It's obvious to us which function you intend to be chosen, but the compiler has to follow the rules of C++ not use clever leaps of logic (or even not so clever ones, as in simple cases like this!)

The relevant constructor of std::function is:

template<class F> function(F f); 

which is a template that accepts any type.

The C++14 standard does constrain the template (since LWG DR 2132) so that it:

shall not participate in overload resolution unless f is Callable (20.9.12.2) for argument types ArgTypes... and return type R.

which means that the compiler will only allow the constructor to be called when Functor is compatible with the call signature of the std::function (which is void(int, int) in your example). In theory that should mean that void add(A, A) is not a viable argument and so "obviously" you intended to use void add(int, int).

However, the compiler can't test the "f is Callable for argument types ..." constraint until it knows the type of f, which means it needs to have already disambiguated between void add(int, int) and void add(A, A) before it can apply the constraint that would allow it to reject one of those functions!

So there's a chicken and egg situation, which unfortunately means that you need to help the compiler out by specifying exactly which overload of add you want to use, and then the compiler can apply the constraint and (rather redundantly) decide that it is an acceptable argument for the constructor.

It is conceivable that we could change C++ so that in cases like this all the overloaded functions are tested against the constraint (so we don't need to know which one to test before testing it) and if only one is viable then use that one, but that's not how C++ works.

like image 118
Jonathan Wakely Avatar answered Sep 17 '22 20:09

Jonathan Wakely