Vladyslav Mozhvylo
Vladyslav Mozhvylo

Reputation: 331

Deduce callback arguments type and use them as return type

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?

Godbolt link.

#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

Answers (1)

max66
max66

Reputation: 66230

Non sure to understand what do you exactly want but... what about a main call() that receive std::functions

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

Related Questions