0

tl;dr:

I am writing a utility function that wraps std::async while catching all exceptions.

#include <functional>
#include <future>
#include <iostream>
#include <string>
#include <thread>

namespace Async {

namespace detail {
template <typename F, typename... Args>
std::invoke_result_t<F, Args...> RunWithLogging(std::string label, F&& f,
                                                Args&&... args) {
  try {
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
  } catch (std::exception& ex) {
    std::cout << label << "Exception escaped thread \"" << label
              << "\": " << ex.what();
    throw;
  } catch (...) {
    std::cout << label << "Exception escaped thread \"" << label
              << "\": (non-standard exception type)";
    throw;
  }
}
}  // namespace detail

/// Like std::async(std::launch::async, f, args...):
/// - Catches and logs any escaped exceptions
/// - Returned future joins the thread on destructor
template <typename F, typename... Args>
[[nodiscard]] std::future<std::invoke_result_t<F, Args...>> Launch(
    std::string label, F&& f, Args&&... args) {
  return std::async(
      std::launch::async, [label = std::move(label), f = std::forward<F>(f),
                           ... args = std::forward<Args>(args)]() mutable {
        return detail::RunWithLogging(std::move(label), std::forward<F>(f),
                                      std::forward<Args>(args)...);
      });
}

}  // namespace Async

Instead of that double-forwarding mutable lambda, I thought I could save a few lines and just use a function pointer using async's parameter-passing signature:

template <typename F, typename... Args>
[[nodiscard]] std::future<std::invoke_result_t<F, Args...>> Launch(
    std::string label, F&& f, Args&&... args) {
  return std::async(std::launch::async, &detail::RunWithLogging,
                    std::move(label), std::forward<F>(f),
                    std::forward<Args>(args)...);
  ;
}

This doesn't compile, for these reasons:

C:\...\lib\Async\Thread.h(33,15): error C2672: 'async': no matching overloaded function found [C:\...\build\lib\Async\AsyncTest.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.34.31933\include\future(1535,81): message : could be 'std::future<_Select_invoke_traits<decay<_Ty>::type,decay<_ArgTypes>::type...>::type> std::async(_Fty &&,_ArgTypes &&...)' [C:\...\build\lib\Async\AsyncTest.vcxproj]
C:\...\lib\Async\Thread.h(33,15): message : 'std::future<_Select_invoke_traits<decay<_Ty>::type,decay<_ArgTypes>::type...>::type> std::async(_Fty &&,_ArgTypes &&...)': could not deduce template argument for '_ArgTypes &&' from 'overloaded-function' [C:\...\build\lib\Async\AsyncTest.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.34.31933\include\future(1522,81): message : or       'std::future<_Select_invoke_traits<decay<_Ty>::type,decay<_ArgTypes>::type...>::type> std::async(std::launch,_Fty &&,_ArgTypes &&...)' [C:\...\build\lib\Async\AsyncTest.vcxproj]
C:\...\lib\Async\Thread.h(32,47): message : 'std::future<_Select_invoke_traits<decay<_Ty>::type,decay<_ArgTypes>::type...>::type> std::async(std::launch,_Fty &&,_ArgTypes &&...)': could not deduce template argument for '_Fty' [C:\...\build\lib\Async\AsyncTest.vcxproj]
C:\...\lib\Async\ThreadTest.cpp(24,57): message : see reference to function template instantiation 'std::future<int> Async::Launch<int(__cdecl *)(int),int>(std::string,F &&,int &&)' being compiled [C:\...\build\lib\Async\AsyncTest.vcxproj]
          with
          [
              F=int (__cdecl *)(int)
          ]

What am I doing wrong? I've tried:

  • Adding std::decay_t<> to a invoke_result_t<...>'s parameters
  • Explicitly specifying the template args for RunWithLogging
  • &detail::RunWithLogging or detail::RunWithLogging
  • Using auto return types
MHebes
  • 2,290
  • 1
  • 16
  • 29
  • 2
    `&detail::RunWithLogging` is a template function. The compiler won't know what template arguments to use to instantiate the template function. – Eljay Jan 10 '23 at 16:49
  • @Eljay I tried ` &detail::RunWithLogging` as well, and it gives a similar but slightly different error: https://godbolt.org/z/veojPPeMb – MHebes Jan 10 '23 at 16:59
  • I dont understand what you are doing. An exception thrown by the function executed asynchronously is already handled by the returned `future`, you get it rethrown when calling `get`. see example here: https://en.cppreference.com/w/cpp/thread/future – 463035818_is_not_an_ai Jan 10 '23 at 17:42
  • @MHebes The problem with `&detail::RunWithLogging` is that you are accidentally passing the deduced types of the parameter passed to `Launch`, not the parameter types of the function `f`. For instance, with `Launch("Lambda", lambda, 1, 2)`, `Args` are `int&&`, which does not match the argument types of `lambda`(`int`s) – Ranoiaetep Jan 10 '23 at 17:43

1 Answers1

0

I believe that the issue lies with references not being passed to threads and with the function argument to std::async not being fully specialized. I made the following changes.

I specialized detail::RunWithLogging and passed label by value:

return std::async(std::launch::async, &detail::RunWithLogging<F,Args...>,
label, std::forward<F>(f),
std::forward<Args>(args)...);

Then I added std::ref to the lambda function.

auto lambda = [](int a, int b) { return a + b; };
auto f = Async::Launch("Lambda", std::ref(lambda), 1, 2);
EXPECT_EQ(f.get(), 3);

And finally I wrapped hi with std::ref, which fixed all compilation issues.

std::string hi = "hello";
auto f =
    Async::Launch("SomethingWithConstRef", &SomethingWithConstRef, std::ref(hi));
EXPECT_EQ(f.get(), "hello!");

Lambdas are nice because they capture references without needing to use std::ref.

See this other post for more information.

jecs
  • 36
  • 1
  • 6