Reputation: 331
I have some function that can take some arguments and callable object(last argument) as callback with results. I want to transform it to a regular call. I tried this approach and it works, but I must specify arguments types every time.
Is there a solution that can help me with automatically deducing callback arguments types and transform them into return type from function?
#include <functional>
#include <iostream>
void foo1(std::function<void(int)> &&f) {
f(42);
}
void foo1(std::string arg, std::function<void(std::string)> &&f) {
f(std::move(arg));
}
void foo2(std::function<void(float)> &&f) {
f(42.42f);
}
template <typename F>
void foo3(F &&f) {
f(42);
}
template <typename Arg>
auto call(auto &&f) {
Arg res;
f([&res](Arg arg) {
res = std::move(arg);
});
return res;
}
int main() {
std::cout << call<float>([](auto &&callback) { foo2(callback); }) << std::endl;
std::cout << call<int>([](auto &&callback) { foo1(callback); }) << std::endl;
std::cout << call<std::string>([](auto &&callback) { foo1("hello", callback); }) << std::endl;
// should work not only with std::function
std::cout << call<int>([](auto &&callback) { foo3(callback); }) << std::endl;
// is there a way to automatically deduce return type from callback?
// std::cout << call<auto>([](auto &&callback) { foo2(callback); }) << std::endl;
// std::cout << call<auto>([](auto &&callback) { foo3(callback); }) << std::endl;
// // this shouldn't compile, cause of ambiguous call
// std::cout << call<auto>([](auto &&callback) { foo1(callback); }) << std::endl;
}
If possible, I also want to return tuple with results if callback has multiple arguments
void foo4(std::function<void(float, int)> &&f) {
f(42.42f, 42);
}
auto [a, b] = call<auto>([](auto &&callback) { foo4(callback); });
I'll be appreciated for any help with this.
Upvotes: 2
Views: 307
Reputation: 66230
Non sure to understand what do you exactly want but... what about a main call()
that receive std::function
s
template <typename Arg>
auto call(std::function<void(std::function<void(Arg)>&&)> const & f)
{
Arg res;
f([&](Arg arg){ res = std::move(arg); });
return res;
}
plus an auxiliary call()
for functions (to permit type deduction, when possible)
template <typename Arg>
auto call (void(f)(std::function<void(Arg)>&&))
{ return call(std::function{f}); }
plus an auxiliary call()
, with concept, for lambdas and other callables (but without type deduction)
template <typename Arg, typename T>
concept FuncFunctionable = requires (T a)
{ std::function<void(std::function<void(Arg)>&&)>{a}; };
template <typename Arg, typename L>
auto call (L const & f) requires FuncFunctionable<Arg, L>
{ return call<Arg>(std::function<void(std::function<void(Arg)>&&)>{f}); }
Given this, you use call()
as follows (two different calls for the foo1()
function with std::string
call(foo2)
call(foo1)
call<std::string>([](std::function<void(std::string)> f) { foo1("hello", std::move(f)); })
call<std::string>([](auto f) { foo1("hello", std::move(f)); })
call<int>(foo3)
The std::tuple
version becomes
template <typename ... Args>
auto call(std::function<void(std::function<void(Args...)>&&)> const & f)
{
std::tuple<Args...> res;
f([&](Args && ... as){ res = std::make_tuple(std::move(as)...); });
return res;
}
template <typename ... Args>
auto call (void(f)(std::function<void(Args...)>&&))
{ return call(std::function{f}); }
template <typename ... Args, typename T>
concept FuncFunctionable = requires (T a)
{ std::function<void(std::function<void(Args...)>&&)>{a}; };
template <typename ... Args, typename L>
auto call (L const & f) requires FuncFunctionable<Args..., L>
{ return call<Args...>(std::function<void(std::function<void(Args...)>&&)>{f}); }
The following is a full compiling example
#include <functional>
#include <iostream>
void foo1 (std::function<void(int)> && f)
{ f(42); }
void foo1 (std::string arg, std::function<void(std::string)> && f)
{ f(std::move(arg)); }
void foo2 (std::function<void(float)> && f)
{ f(42.42f); }
template <typename F>
void foo3 (F && f)
{ f(42); }
void foo4 (std::function<void(float, int)> && f)
{ f(42.42f, 42); }
template <typename ... Args>
auto call(std::function<void(std::function<void(Args...)>&&)> const & f)
{
std::tuple<Args...> res;
f([&](Args && ... as){ res = std::make_tuple(std::move(as)...); });
return res;
}
template <typename ... Args>
auto call (void(f)(std::function<void(Args...)>&&))
{ return call(std::function{f}); }
template <typename ... Args, typename T>
concept FuncFunctionable = requires (T a)
{ std::function<void(std::function<void(Args...)>&&)>{a}; };
template <typename ... Args, typename L>
auto call (L const & f) requires FuncFunctionable<Args..., L>
{ return call<Args...>(std::function<void(std::function<void(Args...)>&&)>{f}); }
int main ()
{
auto [a1] = call(foo2);
auto [b1] = call(foo1);
auto [c1] = call<std::string>([](std::function<void(std::string)> f) { foo1("hello", std::move(f)); });
auto [d1] = call<std::string>([](auto f) { foo1("hello", std::move(f)); });
auto [e1] = call<int>(foo3);
auto [f1, f2] = call(foo4);
std::cout << a1 << std::endl;
std::cout << b1 << std::endl;
std::cout << c1 << std::endl;
std::cout << d1 << std::endl;
std::cout << e1 << std::endl;
std::cout << f1 << ", " << f2 << std::endl;
}
Upvotes: 1