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
.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With