John
John

Reputation: 3534

Why `std::async` itself could not find suitable overloading, whereas lambda could?

Note: I don't ask about how to make the below code snippet work, there are already some posts about that question. What am I conscious about is why std::async itself could not find matched function, whereas lambda could? You see the compiler complains that there is no matching function for call to

'async(std::launch, <unresolved overloaded function type>, std::shared_ptr<Demo>, int)'

, whereas lambda(i.e. [ptr=shared_from_this()](){return ptr->foo(2);) could find suitable overloading.

Why?

Here is the code snippet:

#include <future>
#include <functional>
#include <memory>

class Demo:public std::enable_shared_from_this<Demo> 
{
public:
    int foo(){return 0;};
    int foo(int a){return 0;};

    std::future<int> AsyncFoo1()
    {
        return std::async(std::launch::async, &Demo::foo, shared_from_this(), 2); //Why this line does not compile, whereas `AsyncFoo2()` which uses lambda works well ? I know the blow one is right.
        //return std::async(std::launch::async, static_cast<int(Demo::*)(int)>(&Demo::foo), shared_from_this(), 2); 
    }

    std::future<int> AsyncFoo2()
    {
        return std::async(std::launch::async, [ptr=shared_from_this()](){return ptr->foo(2);});
    }
};

int main()
{

}

Here is what the compiler complains:

<source>: In member function 'std::future<int> Demo::AsyncFoo1()':
<source>:13:26: error: no matching function for call to 'async(std::launch, <unresolved overloaded function type>, std::shared_ptr<Demo>, int)'
   13 |         return std::async(std::launch::async, &Demo::foo, shared_from_this(), 2); //Why this line does not compile, whereas `AsyncFoo2()` which uses lambda works well ? I know the blow one is right.
      |                ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from <source>:1:
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/future:1769:5: note: candidate: 'template<class _Fn, class ... _Args> std::future<typename std::__invoke_result<typename std::decay<_Tp>::type, typename std::decay<_Args>::type ...>::type> std::async(launch, _Fn&&, _Args&& ...)'
 1769 |     async(launch __policy, _Fn&& __fn, _Args&&... __args)
      |     ^~~~~
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/future:1769:5: note:   template argument deduction/substitution failed:
<source>:13:26: note:   couldn't deduce template parameter '_Fn'
   13 |         return std::async(std::launch::async, &Demo::foo, shared_from_this(), 2); //Why this line does not compile, whereas `AsyncFoo2()` which uses lambda works well ? I know the blow one is right.
      |                ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/future:1803:5: note: candidate: 'template<class _Fn, class ... _Args> std::future<typename std::__invoke_result<typename std::decay<_Tp>::type, typename std::decay<_Args>::type ...>::type> std::async(_Fn&&, _Args&& ...)'
 1803 |     async(_Fn&& __fn, _Args&&... __args)
      |     ^~~~~
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/future:1803:5: note:   template argument deduction/substitution failed:
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/future: In substitution of 'template<class _Fn, class ... _Args> std::future<typename std::__invoke_result<typename std::decay<_Tp>::type, typename std::decay<_Args>::type ...>::type> std::async(_Fn&&, _Args&& ...) [with _Fn = std::launch; _Args = {}]':
<source>:13:26:   required from here
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/future:1803:5: error: no type named 'type' in 'struct std::__invoke_result<std::launch>'

Upvotes: 2

Views: 217

Answers (1)

Quimby
Quimby

Reputation: 19223

This is a limitation of the language. The issue is actually that one cannot take an address of overload set, only of functions, it is unrelated to std::async. Therefore if the foo method is overloaded &Demo::foo is invalid. Practically speaking, the same issue happens if foo is a function template and you do not provide the template arguments explicitly.

It also means that one cannot make a perfect wrapper in the language to defer a callbecause that is what std::async ideally wants to do.

Lambdas sidestep this issue all together, you never take the address in the lambda, you just call the function with normal syntax which has overload resolution baked in by the language. This works because lambdas are AFAIK the only construct which allows to dynamically create a type and a function(call operator) in an expression.

Furthermore, lambdas can be passed around because they are just objects.

Lambdas are even more powerful because you can templatize the call and still pass the lambda around exactly for the same reason - you pass objects, one never takes the address of templated operator().

There is no macro-less solution for this so far, it must come from the language. There are some proposals for this, I am aware of P1170R0, unfortunately it has not been accepted so far. The last mention I've found is from github

CONSENSUS: LEWGI would like a variadic std::overload_set library API (as described by P1772). The authors of P1170 and P1772 should collaborate to achieve this goal.

The proposal also contains one temporary solution to construct a new lambda on demand from the name of the function:

#define FWD(x) static_cast<decltype(x)&&>(x)
#define RETURNS(expr) noexcept(noexcept(expr)) -> decltype(expr) { return expr; }
#define OVERLOADS_OF(name) [&](auto&& ...args) RETURNS(name(FWD(args)...))

But I am not sure it can be easily adapted to member functions, especially if you call them through a shared ptr.

Upvotes: 3

Related Questions