std::function fails to distinguish overloaded func

2020-01-27 04:47发布

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.

5条回答
Viruses.
2楼-- · 2020-01-27 05:29

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,

查看更多
beautiful°
3楼-- · 2020-01-27 05:31

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)

查看更多
Rolldiameter
4楼-- · 2020-01-27 05: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.

查看更多
别忘想泡老子
5楼-- · 2020-01-27 05:35

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

查看更多
Animai°情兽
6楼-- · 2020-01-27 05:37

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.

查看更多
登录 后发表回答