Std::Function Fails to Distinguish Overloaded Functions

std::function fails to distinguish overloaded functions

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.

Why doesn't std::function participate in overload resolution?

This doesn't really have anything to do with "phases of translation". It's purely about the constructors of std::function.

See, std::function<R(Args)> doesn't require that the given function is exactly of the type R(Args). In particular, it doesn't require that it is given a function pointer. It can take any callable type (member function pointer, some object that has an overload of operator()) so long as it is invokable as if it took Args parameters and returns something convertible to R (or if R is void, it can return anything).

To do that, the appropriate constructor of std::function must be a template: template<typename F> function(F f);. That is, it can take any function type (subject to the above restrictions).

The expression baz represents an overload set. If you use that expression to call the overload set, that's fine. If you use that expression as a parameter to a function that takes a specific function pointer, C++ can whittle down the overload set to a single call, thus making it fine.

However, once a function is a template, and you're using template argument deduction to figure out what that parameter is, C++ no longer has the ability to determine what the correct overload in the overload set is. So you must specify it directly.

Wrap overloaded function via std::function

That is ambiguous situation.

To disambiguate it, use explicit cast as:

typedef int (*funtype)(const std::string&);

std::function<int(const std::string&)> func=static_cast<funtype>(test);//cast!

Now the compiler would be able to disambiguate the situation, based on the type in the cast.

Or, you can do this:

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.

Overloading on std::function...

Is this supposed to happen or just a deficiency in the compilers?

This is supposed to happen. std::function has a constructor template that can take an argument of any type. The compiler can't know until after a constructor template is selected and instantiated that it's going to run into errors, and it has to be able to select an overload of your function before it can do that.

The most straightforward fix is to use a cast or to explicitly construct a std::function object of the correct type:

func(std::function<void()>([](){}));
func(std::function<void(int)>([](int){}));

If you have a compiler that supports the captureless-lambda-to-function-pointer conversion and your lambda doesn't capture anything, you can use raw function pointers:

void func(void (*param)()) { }
void func(void (*param)(int)) { }

(It looks like you are using Visual C++ 2010, which does not support this conversion. The conversion was not added to the specification until just before Visual Studio 2010 shipped, too late to add it in.)


To explain the problem in a bit more detail, consider the following:

template <typename T>
struct function {

template <typename U>
function(U f) { }
};

This is basically what the std::function constructor in question looks like: You can call it with any argument, even if the argument doesn't make sense and would cause an error somewhere else. For example, function<int()> f(42); would invoke this constructor template with U = int.

In your specific example, the compiler finds two candidate functions during overload resolution:

void func(std::function<void(void)>)
void func(std::function<void(int)>)

The argument type, some unutterable lambda type name that we will refer to as F, doesn't match either of these exactly, so the compiler starts looking at what conversions it can do to F to try and make it match one of these candidate functions. When looking for conversions, it finds the aforementioned constructor template.

All the compiler sees at this point is that it can call either function because

  • it can convert F to std::function<void(void)> using its converting constructor with U = F and
  • it can convert F to std::function<void(int)> using its converting constructor with U = F.

In your example it is obvious that only one of these will succeed without error, but in the general case that isn't true. The compiler can't do anything further. It has to report the ambiguity and fail. It can't pick one because both conversions are equally good and neither overload is better than the other.

C++11 auto, std::function and ambiguous call to overloaded function

std::function had a greedy template constructor that will attempt to construct it from anything at all you passed it1. std::function is not very suitable for use in overload resolution. It worked in one case because perfect matches are preferred to conversion, but as noted it is fragile.

Note that lambdas and std::function objects are unrelated types. std::function knows how to wrap a lambda, but it can wrap any copyable invokable object. Lambdas are auto-created anonymously named classes that are copyable and invokable. std::function is a class designed to type-erase invocation.

Imagine if your overrides took short and long. The auto x = 2.0 and short s = 2.0 would correspond to the auto x = lambda and std::function<blah> f = lambda cases. When you pick the type explicitly you cause a type conversion, and the types you picked explicitly had no ambiguity. But when you did auto it took the real type -- and the real type was ambiguous.

SFINAE or tag dispatching using std::result_of would let you handle these overrides manually.

In c++14 this changed a bit. Now the constructor only tries to swallow compatible arguments. But a function returning int and one returning float are both compatible with each other, so it won't help your specific case.


1 To a certain extent this is a flaw in std::function. Its "universal" constructor should really only participate in overload resolution when the type passed in is both copyable and std::result_of_t< X( Args... ) > can be converted to the result type of the std::function. I suspect post-concepts this will be added to the standard, as post-concepts this is really easy to both write and express (and very little conforming code will be broken by it). However, in this particular case, this would not actually help, as int and float can be converted into each other, and there are no ways to say "this constructor will work, but really it isn't a preferred option".

Why can't `std::async` pick the correct overload?

Overload resolution happens based on types of arguments specified at the call site. When you're calling std::async, you're not calling calc_something but passing a pointer to the function to std::async. There are no calc_something call arguments and no way to resolve which of the two overloads' address to pass to std::async.

The compiler cannot use the subsequent arguments of std::async to resolve the overload. From the compiler's perspective, all std::async arguments are unrelated and nothing implies they will be used to invoke calc_something. In fact, you can call std::async with arguments of types different from those calc_something accepts, and it will work because they will get converted when calc_something is invoked.

In order to resolve this ambiguity, you must explicitly cast the pointer to calc_something to the exact type of the function pointer, matching one of the overloads.

std::async((std::size_t (*)(std::size_t))calc_something, 5);

What makes the overload fail between these two function templates?

Even though this is language-lawyer, I'm going to provide a layman explanation.

Yes, the second overload fixes the first parameter of pair as int, while the first one doesn't.

But, on the other hand, the first overload fixes the second parameter of A as void, while the second one doesn't.

Your functions are equivalent to those:

template <typename T, typename U>
void f(A<std::pair<T, U>, void>) {}

template <typename U>
void f(A<std::pair<int,U>, blah-blah<U>>) {}

So none of them is more specialized than the other.


The code will work if you use more a conventional SFINAE:

template<typename U, std::enable_if_t<std::is_same_v<U, int>, std::nullptr_t> = nullptr>
void f(A<std::pair<int,U>>) {}

Or C++20 concepts:

template <std::same_as<int> U>
void f(A<std::pair<int,U>>) {}

use std::bind with overloaded functions

You may use:

auto f_binder = std::bind(static_cast<double(&)(double)>(f), 2.);

or

auto f_binder = bind<double(double)>(f, 2.);

Alternatively, lambda can be used:

auto f_binder = []() {
return f(2.); // overload `double f(double)` is chosen as 2. is a double.

};

What does Cannot overload functions distinguished by return type alone mean?

Function overloading means to have multiple methods with the same name.

Now, the compiler, to resolve the correct overloaded method, looks at method name and arguments but NO at the return value. This means that if you have

int round(float something) { ... }
float round(float something) { ... }

Then the compiler is not able to distinguish them and know which one you want to invoke at a call point. So in your case this means that there is already another round method which accepts a float.

Why can't decltype work with overloaded functions?

To figure out the type of the function from the type of the arguments you'd pass, you can "build" the return type by using decltype and "calling" it with those types, and then add on the parameter list to piece the entire type together.

template<typename... Ts>
using TestType = decltype(test(std::declval<Ts>()...))(Ts...);

Doing TestType<double, double> will result in the type int(double, double). You can find a full example here.

Alternatively, you might find the trailing return type syntax more readable:

template<typename... Ts>
using TestType = auto(Ts...) -> decltype(test(std::declval<Ts>()...));


Related Topics



Leave a reply



Submit