Search code examples
c++template-meta-programmingc++20template-argument-deduction

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.


Solution

  • 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;
     }