【问题标题】:Deduce callback arguments type and use them as return type推断回调参数类型并将它们用作返回类型
【发布时间】:2021-06-04 01:08:36
【问题描述】:

我有一些函数可以将一些参数和可调用对象(最后一个参数)作为回调结果。我想将其转换为常规通话。我尝试了这种方法并且它有效,但我必须每次都指定参数类型。

有没有一种解决方案可以帮助我自动推断回调参数类型并将它们转换为函数的返回类型?

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

如果可能,如果回调有多个参数,我还想返回带有结果的元组

void foo4(std::function<void(float, int)> &&f) {
    f(42.42f, 42);
}

auto [a, b] = call<auto>([](auto &&callback) { foo4(callback); });

如果能提供任何帮助,我们将不胜感激。

【问题讨论】:

  • 在您的示例中,call 始终采用 lambda。将 lambdas 传递给call 的解决方案是否足够?还是call 需要接受任何可调用的?
  • 我猜 lambda 就足够了。
  • 好吧,如果我能像这样使用就完美了 call(foo1, "arg");但我想它已经太复杂了,如果它完全可以处理重载,我也想知道
  • call&lt;float&gt;([](auto &amp;&amp;callback) { foo2(callback); }) 可以是call&lt;float&gt;(foo2),从foo2,我们可以知道输入参数(而通用 lambdas 允许任何类型)。
  • [](auto){..},你不能推断出任何东西,auto 可以是任何东西(即使主体随后会因无效参数而失败)。 call(&amp;foo2) 很简单,没有重载,不涉及模板,所以我们可以推断出foo2 的参数,foo1 有重载,所以call(&amp;foo1) 是模棱两可的。类似call(&amp;foo3),你想要哪个T

标签: c++ template-meta-programming c++20 template-argument-deduction


【解决方案1】:

不确定您到底想要什么,但是...接收std::functions 的主要call() 怎么样

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

为函数加上一个辅助call()(如果可能,允许类型推断)

template <typename Arg>
auto call (void(f)(std::function<void(Arg)>&&))
 { return call(std::function{f}); }

加上一个辅助 call(),带有概念,用于 lambda 和其他可调用对象(但没有类型推导)

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

鉴于此,您可以按如下方式使用call()foo1() 函数与 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)

std::tuple 版本变为

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

以下是完整的编译示例

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

【讨论】:

    猜你喜欢
    • 2017-10-15
    • 1970-01-01
    • 1970-01-01
    • 2020-11-05
    • 2020-12-26
    • 1970-01-01
    • 2016-07-23
    • 1970-01-01
    • 2020-03-12
    相关资源
    最近更新 更多