2

Please consider the following example:

#include <iostream>
#include <future>

std::size_t calc_something(std::size_t lim_)
{   
    std::size_t result = lim_ * 10;
    return result;
}

void calc_something(std::size_t lim_, std::promise<std::size_t> &promise_)
{   
    std::size_t result = lim_ * 10;
    promise_.set_value(result);
}

void async_calc()
{
    std::future<std::size_t> async_calc = std::async(calc_something, 5);
    std::cout<< "async_calc = " << async_calc.get() <<std::endl;
}

I am still new to multi-threading, but why -on earth- can't std::async pick the correct overload? The second overload uses a reference to an std::promise object.

I've looked at this question here but it doesn't explain why. Also, I do not get an ambiguity error.

The error I get is:

error: no matching function for call to 'async' std::future<std::size_t> async_calc = std::async(calc_something, 5);
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
Constantinos Glynos
  • 2,952
  • 2
  • 14
  • 32

3 Answers3

4

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);
Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
  • It's useful to write a simple helper template, e.g. `overload` or `overload_of` so you can write `overload(calc_something)` which is a lot more self-explanatory than an explicit function pointer cast. – rubenvb Feb 03 '20 at 15:19
  • 1
    You will also have to specify return type in template parameters. And you would also have to make a similar helper for member functions. At this point it's not really better than a cast. – Andrey Semashev Feb 03 '20 at 15:25
  • @AndreySemashev: You say that there are no `calc_something` call arguments so I understand why a function pointer itself could cause problems. However, the second argument in the `std::async` is the argument to the function. Why can't `std::async` use that to determine the correct overload??? – Constantinos Glynos Feb 03 '20 at 16:25
  • 1
    @AndreySemashev if `overload` is a class template, with a template static member function `of`, then `overload::of(&calc_something)` can deduce the return type from it's argument – Caleth Feb 03 '20 at 16:29
  • It's not `std::async` who does the resolution, it's the compiler, and `std::async` is not special. 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. – Andrey Semashev Feb 03 '20 at 16:31
  • Aaahhh! Now THAT makes sense! Could you please add this sentence: "From the compiler's perspective, all std::async arguments are unrelated and nothing implies they will be used to invoke calc_something." in your answer so that I can accept it! Thanks! I thought `std::async` would use those args to determine the correct overload. Since they are all unrelated, then it's pretty obvious why it can't figure it out. – Constantinos Glynos Feb 03 '20 at 16:32
  • @Caleth I don't think I understand how. – Andrey Semashev Feb 03 '20 at 16:32
  • 2
    @AndreySemashev have a look at `QOverload` https://github.com/qt/qtbase/blob/dev/src/corelib/global/qglobal.h – Caleth Feb 03 '20 at 16:33
3

Overload sets cannot be sent as parameter as is, it must be converted to a function pointer first, or must be lifted into an object.

Overload sets must be converted since only the name of the function denote the complete set of functions, but you must send only one of those. How the compiler chooses the right one depends on the arguments you sent to it. Calling it provides the type of the parameters, but converting to a function pointer also provide the compiler with enough information about which overload must be sent.

Converting to a function pointer is usually the easy way:

auto function = static_cast<std::size_t(*)(std::size_t)>(calc_something);
std::future<std::size_t> async_calc = std::async(function, 5);

Lifting is done using a lambda:

std::future<std::size_t> async_calc = std::async([](auto lim) { return calc_something(lim); }, 5);

Lifting here is possible since you call the overload set, so there is a parameter the compiler can choose.

It just cannot delay that decision for overload sets, but for lambda and template function it can.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
2

std::async() is a function template and you pass calc_something as an argument to it. However, there are two functions called calc_something due to overloading. The calc_something() overload has to be selected before the template deduction of std::async()'s arguments takes place.

std::future<std::size_t> async_calc = std::async(calc_something, 5);

In the code above, which overload will be passed to std::async()?

std::size_t calc_something(std::size_t);
void calc_something(std::size_t, std::promise<std::size_t> &);

You have to specify:

std::async(static_cast<std::size_t(*)(std::size_t)>(calc_something), 5);
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
JFMR
  • 23,265
  • 4
  • 52
  • 76
  • I would suspect it would use the overload with only one parameter since I only give one extra argument to `std::async` the fist one being the function pointer. Why doesn't `std::async` use these arguments to determine the correct overload? – Constantinos Glynos Feb 03 '20 at 16:27
  • @ConstantinosGlynos It can't, all your second argument (i.e., `5`) does in the call to `std::sync()` is to make the corresponding template argument to be deduced to `int`, nothing else. It doesn't participate in the deduction of the template argument corresponding to the first parameter. – JFMR Feb 03 '20 at 16:30
  • 1
    Yep! Nice one. Same as the answer above, so +1. Thanks! – Constantinos Glynos Feb 03 '20 at 16:35