std::function fails to distinguish overloaded functions
Asked Answered
N

5

43

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.

Nap answered 22/5, 2015 at 9:37 Comment(5)
Which compiler fails this?Spoofery
gcc 4.8, clang and Visual Studio 2013. I can post the compiler errors if that is required though they are not very friendly.Nap
compiler errors usually are not friendly but worth posting it, it's strange which this becomes ambiguous here, almost as if the type of class A is somehow viewed as being the same as int, does the error still occur if you declare add as taking doubles?Spoofery
Yes. It fails when I change the parameter types to double in the second function. I have edited the question to include the errors that I am getting on clang.Nap
similar question and its answer: https://mcmap.net/q/390905/-setting-a-std-function-variable-to-refer-to-the-std-sin-functionGalton
M
32

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.

Masterly answered 22/5, 2015 at 10:48 Comment(4)
Thank you so much Jonathan Wakely. I had edited the question before you posted this answer to indicate that I found out about the templatized constructor from another related question. But your answer is the closest to what I wanted. Thank you so much for the clarification.Nap
The standard does require trial deductions when the argument is an overload set, but only if the parameter is of pointer to function, pointer to member function, or function type (see [temp.deduct.call]/p6) after stripping top-level cv-qualifiers and referenceness.Rattlepate
@Rattlepate It's a very interesting idea, but only deduction failure is allowed in selecting the overload, not substitution failure. So there's no way to apply that mechanism in such a generic context as std::function.Wolf
IMO it's very bad that C++ does not check all possible overloads.Stendhal
C
20

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)

Companionable answered 22/5, 2015 at 9:51 Comment(7)
Thanks MSalters. Is this something that will change in the future? Overload resolution seems like a valid thing to do here because while creating the 'function' object, I am specifying exactly the type of callable objects that I want. I understand that function can handle classes with overloaded operator() or lambdas or regular functions. But shouldn't overload resolution (though strictly speaking there is no overload resolution here as lambdas and classes dont essentially overload a regular function) or something similar be applicable hereNap
@user3493289: I wouldn't expect it to change in the future. Note that if int is implicitly convertible to A and back, then std::function<int(int,int)> can also hold A add(A,A). It's that flexibility which makes overloading not just technically, but also logically impossible.Companionable
Huh. I have written so many function-oid classes I apparently forgot that std::function<void(int,int)> is missing a void(*)(int,int) overload. Wouldn't such an overload be harmless, and cause the above to work?Broeker
@Yakk: You'd still have two ctors of ` std::function<>. How would you do overload resolution between the two, given &add` (which at that point is still trying to take the address of an overload set)? So, no, the ctor overload resolution has to happen after the overload resolution of &add and vice versa, which is logically impossible.Companionable
@msalt except it works. I was not asking if it works: I know it does in practice. I was asking if there where downsides.Broeker
Just spotted what I overlooked before: Overload resolution of the function ctors succeeds, because the templated ctor fails silently (SFINAE) when you pass &add.Companionable
@user3493289: The C++ style nowadays suggests adding std::function<F> function_from_ptr<F>(F* funptr) since you can then write auto f = function_from_ptr<void()(int,int)>(&add). The pattern of make_shared and make_unique.suggests make_function but see #27826059Companionable
E
8

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.

Earvin answered 22/5, 2015 at 9:43 Comment(2)
Hi, I know how to work around this. But my question is "Why is there an ambiguity in the first place"? Aren't function parameters essentially part of the signature which should help choose the correct function?Nap
Thanks for showing a workaround. Didn't know how to deal with this. We could say that traditional function pointers have an advantage over std::function in this use case due to "automatic" overload resolution?Atrocity
E
3

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,

Elana answered 11/1, 2017 at 5:0 Comment(0)
T
-6

As far as I can see, it's a Visual Studio problem.

c++11 standard (20.8.11)

std::function synopsis
template<class R, class... ArgTypes> class function<R(ArgTypes...)>;

but VisualStudio doesn't have that specialization

clang++ and g++ are perfectly fine with overloading std::functions

prior answers explain why VS doesn't work, but they didn't mention that it's VS' bug

Tubb answered 3/5, 2016 at 18:20 Comment(3)
Prior answers give explanations that indicate your answer is incorrect. Do you have an example that shows how your response adds something to the answers already posted?Interchangeable
Prior answer explain bad VS behavior, that's all. They mislead. It's as simple as that, please, read standardTubb
"clang++ and g++ are perfectly fine with overloading std::functions" No, they aren't. It does not work on any compiler.Stendhal

© 2022 - 2024 — McMap. All rights reserved.