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
.
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 typesArgTypes...
and return typeR
.
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.
While it's obvious what you want, the problem is that std::function
cannot influence overload resolution of &add
. If you were to initialize a raw function pointer (void (*func)(int,int) = &add
), it does work. That's because function pointer initialization is a context in which overload resolution is done. The target type is exactly known. But std::function
will take almost any argument that's callable. That flexibility in accepting arguments does mean that you can't do overload resolution on &add
. Multiple overloads of add
might be suitable.
An explicit cast will work, i.e. static_cast<void(*)(int, int)> (&add)
.
This can be wrapped in a template<typename F> std::function<F> make_function(F*)
which would allow you to write auto func = make_function<int(int,int)> (&add)
Try:
std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);
Addresses to void add(A, A)
and void add(int, int)
obvoiusly differes. When you point to the function by name it is pretty much imposible for compiler to know which function address do you need. void(int, int)
here is not a hint.
Another way to deal with this is with a generic lambda in C++14:
int main() {
std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...);
}
That will create a lambda function that will resolve things with no ambiguity. I did not forward arguments,