Changing std::async to use function pointer instead of lambda - c++

tl;dr:
Working version: https://godbolt.org/z/fsxEeGf6W
Broken version (why?): https://godbolt.org/z/KoooxsqoW
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

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.

Related

How to check a valid boost asio CompletionToken?

I'm writing Boost.Asio style async function that takes CompletionToken argument.
The argument can be function, function object, lambda expression, future, awaitable, etc.
CompletionToken is template parameter. If I don't restrict the argument, the function could match unexpected parameter such as int. So I want to write some restriction using std::enable_if. (My environment is C++17).
If CompletionToken takes parameters, then I can use std::is_invocable for checking.
See f1 in my example code.
However, I got a problem. If CompletionToken takes no parameters, then boost::asio::use_future makes an error.
See f2 in my example code.
After some of try and error, I got my solution. That is concatenating std::is_invocable and use_future_t checking by OR (||).
See f3 in my example code.
But it is not so elegant. In addition, I'm not sure other features supported by Boost.Asio e.g.) use_awaitable_t requires similar direct matching check.
I tried to find Boost.Asio provides type traits or predicate such as is_completion_token, but I couldn't find it.
Is there any better way to checking CompletionToken?
Godbolt link https://godbolt.org/z/sPeMo1GEK
Complete Code:
#include <type_traits>
#include <boost/asio.hpp>
// Callable T takes one argument
template <
typename T,
std::enable_if_t<std::is_invocable_v<T, int>>* = nullptr
>
void f1(T) {
}
// Callable T takes no argument
template <
typename T,
std::enable_if_t<std::is_invocable_v<T>>* = nullptr
>
void f2(T) {
}
template <template <typename...> typename, typename>
struct is_instance_of : std::false_type {};
template <template <typename...> typename T, typename U>
struct is_instance_of<T, T<U>> : std::true_type {};
// Callable T takes no argument
template <
typename T,
std::enable_if_t<
std::is_invocable_v<T> ||
is_instance_of<boost::asio::use_future_t, T>::value
>* = nullptr
>
void f3(T) {
}
int main() {
// no error
f1([](int){});
f1(boost::asio::use_future);
// same rule as f1 but use_future got compile error
f2([](){});
f2(boost::asio::use_future); // error
// a little complecated typechecking, then no error
f3([](){});
f3(boost::asio::use_future);
}
Outputs:
Output of x86-64 clang 13.0.1 (Compiler #1)
<source>:45:5: error: no matching function for call to 'f2'
f2(boost::asio::use_future); // error
^~
<source>:17:6: note: candidate template ignored: requirement 'std::is_invocable_v<boost::asio::use_future_t<std::allocator<void>>>' was not satisfied [with T = boost::asio::use_future_t<>]
void f2(T) {
^
1 error generated.
If you have c++20 concepts, look below. Otherwise, read on.
When you want to correctly implement the async-result protocol using Asio, you would use the async_result trait, or the async_initiate as documented here.
This should be a reliable key for SFINAE. The template arguments to async_result include the token and the completion signature(s):
Live On Compiler Explorer
#include <boost/asio.hpp>
#include <iostream>
using boost::asio::async_result;
template <typename Token,
typename R = typename async_result<std::decay_t<Token>, void(int)>::return_type>
void f1(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
template <typename Token,
typename R = typename async_result<std::decay_t<Token>, void()>::return_type>
void f2(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
int main() {
auto cb1 = [](int) {};
f1(cb1);
f1(boost::asio::use_future);
f1(boost::asio::use_awaitable);
f1(boost::asio::detached);
f1(boost::asio::as_tuple(boost::asio::use_awaitable));
auto cb2 = []() {};
f2(cb2);
f2(boost::asio::use_future);
f2(boost::asio::use_awaitable);
f2(boost::asio::detached);
f2(boost::asio::as_tuple(boost::asio::use_awaitable));
}
Already prints
void f1(Token&&) [with Token = main()::<lambda(int)>&; R = void]
void f1(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<int>]
void f1(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<int, boost::asio::any_io_executor>]
void f1(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f1(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<int>, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = main()::<lambda()>&; R = void]
void f2(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<void>]
void f2(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<void, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f2(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<>, boost::asio::any_io_executor>]
C++20 Concepts
Now keep in mind the above is "too lax" due to partial template instantiation. Some parts of async_result aren't actually used. That means that f2(cb1); will actually compile.
The linked docs even include the C++20 completion_token_for<Sig> concept that allows you to be precise at no effort: Live On Compiler Explorer
template <boost::asio::completion_token_for<void(int)> Token> void f1(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
template <boost::asio::completion_token_for<void()> Token> void f2(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
Otherwise
In practice you would always follow the Asio recipe, and that guarantees that all parts are used. Apart from the example in the documentation, you can search existing answers
Example:
template <typename Token>
typename asio::async_result<std::decay_t<Token>, void(error_code, int)>::return_type
async_f1(Token&& token) {
auto init = [](auto completion) {
auto timer =
std::make_shared<asio::steady_timer>(boost::asio::system_executor{}, 1s);
std::thread(
[timer](auto completion) {
error_code ec;
timer->wait(ec);
std::move(completion)(ec, 42);
},
std::move(completion))
.detach();
};
return asio::async_result<std::decay_t<Token>, void(error_code, int)>::initiate(
init, std::forward<Token>(token));
}

C++20 - Template argument deduction/substitution problem

I am tring to write the function sql_exec to run a serializable transaction in a Postgresql database using pqxx and I still fail, because of Template argument deduction/substitution problem.
I have tried to rewrite template function many times, but I don't really know, what is wrong and don't understand, why the compiler returns error.
This is the function
namespace kp{
pqxx::connection get_conn();
int txn_rep();
using txn_rlbck = pqxx::transaction_rollback;
using t_txn = pqxx::transaction<pqxx::serializable>;
using t_con = pqxx::connection;
class txn_gen{
t_con& con;
int enu = 0;
public:
txn_gen(t_con& con) : con(con){};
void except(const txn_rlbck& ex);
t_txn txn();
};
template<typename t_function, typename... t_args>
std::invoke_result_t<std::decay_t<t_function>, std::decay_t<t_txn&>, std::decay_t<t_args>...> sql_exec(t_con& conn, t_function&& f, t_args&&... args){
txn_gen gen(conn);
while(true){
try{
auto&& txn = gen.txn();
auto&& ret = f(txn, args...);
txn.commit();
return ret;
}
catch(txn_rlbck& ex){
gen.except(ex);
}
}
}
};
Then I used it in this lambda
auto map_deamon = [this]{
while(true){
std::this_thread::sleep_for(to_duration(settings.update_map_period));
t_con conn = get_conn();
std::unique_lock<std::mutex> lock(map_overload_mutex);
auto&& reload = [this, &lock](t_txn& txn){
mp::tt_map new_map = obtain_map(txn, settings, walk_machine, car_machine);
lock.lock();
map = std::make_shared<mp::tt_map> (std::move(new_map));
};
sql_exec(conn, reload);
}
};
And compiler returned this error
src/path_planner.cpp: In lambda function:
src/path_planner.cpp:61:32: error: no matching function for call to ‘sql_exec(kp::t_con&, kp::server_instance::boot()::<lambda()>::<lambda(kp::t_txn&)>&)’
sql_exec(conn, reload);
^
In file included from src/path_planner.cpp:12:
include/sql.hpp:27:99: note: candidate: ‘template<class t_function, class ... t_args> std::invoke_result_t<typename std::decay<_Tp>::type, pqxx::transaction<(pqxx::isolation_level)2, (pqxx::readwrite_policy)1>, std::decay_t<t_args>...> kp::sql_exec(kp::t_con&, t_function&&, t_args&& ...)’
std::invoke_result_t<std::decay_t<t_function>, std::decay_t<t_txn&>, std::decay_t<t_args>...> sql_exec(t_con& conn, t_function&& f, t_args&&... args){
^~~~~~~~
include/sql.hpp:27:99: note: template argument deduction/substitution failed:
src/path_planner.cpp: In function ‘void kp::boot()’:
Can you suggest me, where the problem is please?
Thank you.
Your method is SFINAEd, as reload(std::declval<t_txn>()) is invalid (reload(std::declval<t_txn&>()) would be valid)
You might use decltype(auto)
template<typename t_function, typename... t_args>
decltype(auto)
sql_exec(t_con& conn, t_function&& f, t_args&&... args)
or get rid of std::decay (especially for t_txn&):
template<typename t_function, typename... t_args>
std::invoke_result_t<t_function, t_txn&, t_args...>
sql_exec(t_con& conn, t_function&& f, t_args&&... args)

Perfect forwarding to function pointer call

I'm trying to compile simple templetized wrapper in Visual Studio 2015
template<typename Rv, typename ...Args>
Rv call(Rv(*fp)(Args...), Args&&...args) {
return (*fp)(std::forward<Args>(args)...);
}
int iArg;
void(*fn)(int);
call(fn, iArg);`
I'm getting the following compiler error:
test.cpp(30): error C2672: 'call': no matching overloaded function found
error C2782: 'Rv call(Rv (__cdecl *)(Args...),Args &&...)': template parameter 'Args' is ambiguous
1> test.cpp(22): note: see declaration of 'call'
1> test.cpp(30): note: could be 'int'
1> test.cpp(30): note: or 'int&'
Why?
Thanks in advance
You have to split args to allow correct deduction:
template <typename Rv, typename... Args, typename... Ts>
Rv call(Rv(*fp)(Args...), Ts&&...args) {
return (*fp)(std::forward<Ts>(args)...);
}
This can be made a bit more generic to call any type of callable thing by having the template parameter be a function type instead of a raw function pointer. Working example for GCC. Should work for visual studio.
#include <iostream>
#include <type_traits>
template<typename Func, typename ...Args>
typename std::result_of<Func(Args...)>::type call(Func fp, Args&&...args) {
return fp(std::forward<Args>(args)...);
}
void foo(int i) {
std::cout << i << std::endl;
}
int main() {
int iArg = 2;
void(*fn)(int) = foo;
call(fn, iArg);
return 0;
}

Unresolved overloaded function type when attempting to pass a function as an argument

I receive the following error when attempting to compile a lone C++ testing file under G++ with C++11 in place.
spike/cur_spike.cpp: In function ‘int main()’:
spike/cur_spike.cpp:60:44: error: no matching function for call to ‘callFunctionFromName(<unresolved overloaded function type>, std::__cxx11::string&)’
callFunctionFromName (outputLine, param);
^
spike/cur_spike.cpp:49:7: note: candidate: template<class funcT, class ... Args> funcT callFunctionFromName(funcT (*)(Args ...), Args ...)
funcT callFunctionFromName (funcT func(Args...), Args... args) {
^
spike/cur_spike.cpp:49:7: note: template argument deduction/substitution failed:
spike/cur_spike.cpp:60:44: note: couldn't deduce template parameter ‘funcT’
callFunctionFromName (outputLine, param);
^
Here's the code.
#include <iostream>
#include <string>
template <typename T>
void outputLine (T text) {
std::cout << text << std::endl;
}
template <typename funcT>
funcT callFunctionFromName (funcT func()) {
return func ();
}
template <typename funcT, typename... Args>
funcT callFunctionFromName (funcT func(Args...), Args... args) {
return func (args...);
}
int main () {
std::string param = "Testing...";
callFunctionFromName (outputLine, param);
return 0;
}
I'm currently building this on a Linux system using G++, this code snippet contains all code related to this issue. I'm particularly intrigued about the fact that the compiler is unable to deduce the template parameter funcT for some reason, even though the outputLine function has a clear return type of void.
Setting a pointer of outputLine using void (*outputLinePtr)(std::string) = outputLine and using the pointer as the argument instead does nothing. Any help would be much appreciated.
You can manually set template argument.
callFunctionFromName (outputLine<std::string>, param);

What pattern matching (if any) is applied for C++ variadic template function invocation?

I just naïvely wrote this:
#include <stdio.h>
template< class... Args >
auto format_for( Args... args, int last_arg )
-> char const*
{
// using Specifier = char const [3];
// static Specifier const s[] = { {'%', 'f'+(0*args), ' '}..., {'%', 'f', 0} };
return reinterpret_cast<char const*>( "" );
}
auto main() -> int
{
printf( "'%s'\n", format_for( 5, 2, 1 ) );
}
It crashes Visual C++ 2015 update 1, an ICE (Internal Compiler Error), and g++ 5.1.0 maintains that the function only takes one argument, presumably as a result of ignoring the Args after failing to match this template parameter:
C:\my\forums\so\081> cl printf.cpp /Feb
printf.cpp
printf.cpp(14): fatal error C1001: An internal error has occurred in the compiler.
(compiler file 'f:\dd\vctools\compiler\cxxfe\sl\p1\cxx\dymto.c', line 6771)
To work around this problem, try simplifying or changing the program near the locations listed above.
Please choose the Technical Support command on the Visual C++
Help menu, or open the Technical Support help file for more information
C:\my\forums\so\081> g++ printf.cpp
printf.cpp: In function 'int main()':
printf.cpp:14:43: error: no matching function for call to 'format_for(int, int, int)'
printf( "'%s'\n", format_for( 5, 2, 1 ) );
^
printf.cpp:4:6: note: candidate: template<class ... Args> const char* format_for(Args ..., int)
auto format_for( Args... args, int last_arg )
^
printf.cpp:4:6: note: template argument deduction/substitution failed:
printf.cpp:14:43: note: candidate expects 1 argument, 3 provided
printf( "'%s'\n", format_for( 5, 2, 1 ) );
^
C:\my\forums\so\081> _
So,
Why does the above not compile with g++?
How can the intent (hopefully evident from the code) be expressed?
More generally, what are the rules for matching a call to declaration of variadic template function?
Args appears in a non-deduced context. In this case, that makes it being deduced as the the empty pack.
If you want to extract the last argument but maintain usual deduction rules, you can write a simple helper:
template <typename U>
constexpr U&& last(U&& u) {return std::forward<U>(u);}
template <typename U, typename... T>
constexpr decltype(auto) last(U&&, T&&... t) {return last(std::forward<T>(t)...);}
Demo.
More generally, what are the rules for matching a call to declaration of variadic template function?
Those are pretty verbose, but function parameter packs that are not trailing will generally either be deduced to empty ones or yield a deduction failure.
In your particular case, try index_sequences:
template <class... Args, std::size_t... indices>
auto format_for( std::index_sequence<indices...>, Args... args )
{
auto tup = std::forward_as_tuple(std::forward<Args>(args)...);
using Specifier = char const [3];
static Specifier const s[] = { {'%', (char)('f'+(0*std::get<indices>(tup))), ' '}..., {'%', 'f', 0} };
int last_arg = std::get<sizeof...(Args)-1>(tup);
return s;
}
template <class... Args>
auto format_for( Args&&... args ) {
return format_for(std::make_index_sequence<sizeof...(Args)-1>{},
std::forward<Args>(args)...);
}
…and hope that the compiler optimizes well - Demo 2. Or go down the cheeky road:
template <class... Args, std::size_t... indices>
auto format_for( std::index_sequence<indices...>, Args... args )
{
using Specifier = char const [3];
static Specifier const s[] = {
{'%', (char)(indices == sizeof...(Args)-1?
'f' : 'f'+(0*args)), ' '}... };
int last_arg = last(args...); // As before
return s;
}
template <class... Args>
auto format_for( Args&&... args ) {
return format_for(std::make_index_sequence<sizeof...(Args)>{},
std::forward<Args>(args)...);
}
Demo 3.