【问题标题】:Template argument and deduction of std::function parametersstd::function 参数的模板参数和推导
【发布时间】:2017-02-19 22:34:10
【问题描述】:

假设有一个模板函数foo() 接受任意数量的参数。鉴于最后一个参数始终是std::function,我如何以CbArgs 包含此std::function 参数的方式实现下面显示的foo() 模板?

template<typename... InArgs, typename... CbArgs = ???>
//                                       ^^^^^^^^^^^^
void foo(InArgs... args) { ... }

例如,CbArgs 应该是 {int,int},如果这样调用:

std::function<void(int,int)> cb;
foo(5, "hello", cb);

我的第一个想法是:

template<typename... InArgs, typename... CbArgs>
void foo(InArgs... args, std::function<void(CbArgs...)>) { ... }

但这不能编译:

note:   template argument deduction/substitution failed:
note:   mismatched types ‘std::function<void(CbArgs ...)>’ and ‘int’
  foo(5, "hello", cb);

问题一:
为什么不编译?为什么模板参数推导失败?


最终,我想出了这个解决方案:

template<typename... InArgs, typename... CbArgs>
void fooImpl(std::function<void(CbArgs...)>, InArgs... args) { ... }

template<typename... InArgs,
         typename CbType = typename std::tuple_element_t<sizeof...(InArgs)-1, std::tuple<InArgs...>>>
void foo(InArgs... args)
{
    fooImpl(CbType{}, args...);
}

这里CbTypeInArgs 中的最后一个类型,即std::function。然后将 CbType 的临时值传递给 fooImpl(),其中推导出 CbArgs。这行得通,但在我看来很难看。

问题二:
我想知道没有两个函数和CbType的临时实例是否有更好的解决方案?

【问题讨论】:

  • 但该功能是否必须位于最后位置?还是可以排第一?
  • 它必须在最后一个位置。这是包装异步函数所必需的,这些函数的最后一个参数是回调。
  • "必须" -- 对不起,我可以编写一个没有回调作为最后一个参数的异步函数。那么它以何种方式“必须”将回调作为其最后一个参数?只是你更喜欢连续传球风格吗?你认为“接下来会发生什么”是在“你在做什么”之后?原因有很多,但是“异步函数必须有一个回调作为最后一个参数”跳过了原因,只是陈述了你的结论。
  • 您是否看过 n3865 并考虑过这种方法?
  • 好吧,让我换个说法。从技术上讲,回调不必是最后一个参数。但通常是最后一个参数。如果你认为我错了,请给我在现实生活中相反的例子。无论如何,我想要的只是编写一个包装器,它将许多已经编写的函数转换为最后一个位置的回调函数,这些函数将返回future。例如。 convertToFuture(&amp;foo, 5, "hello") 会调用foo(5, "hello", &lt;some lambda&gt;) 并返回future&lt;tuple&lt;int,int&gt;&gt;,这在调用回调时实现。

标签: c++ c++14 variadic-templates template-argument-deduction


【解决方案1】:

为什么不编译?为什么模板参数推导失败?

当一个参数包不是最后一个参数时,不能推导出来。告诉编译器 InArgs... 的内容将使您的 foo 定义工作:

template<typename... InArgs, typename... CbArgs>
void foo(InArgs..., std::function<void(CbArgs...)>) { }

int main()
{
    std::function<void(int,int)> cb;
    foo<int, const char*>(5, "hello", cb);
}

或者,正如您在解决方法中发现的那样,只需将 InArgs... 放在末尾并更新您的 foo 调用:

template<typename... InArgs, typename... CbArgs>
void foo(std::function<void(CbArgs...)>, InArgs...) { }

int main()
{
    std::function<void(int,int)> cb;
    foo(cb, 5, "hello");
}

不知道没有两个函数和CbType的临时实例是否有更好的解决方案?

这是一种避免不必要的临时实例但使用相同机制来推断CbArgs... 的可能方法:只需将CbType 包装在一个空包装器中,然后将其传递给fooImpl

template <typename T>
struct type_wrapper
{
    using type = T;
};

template<typename... InArgs, typename... CbArgs>
void fooImpl(type_wrapper<std::function<void(CbArgs...)>>, InArgs&&...) { }

template<typename... InArgs,
         typename CbType = 
             std::tuple_element_t<sizeof...(InArgs)-1, 
                 std::tuple<std::remove_reference_t<InArgs>...>>>
void foo(InArgs&&... args)
{
    fooImpl(type_wrapper<CbType>{}, std::forward<InArgs>(args)...);
}

其他改进:

  • typename CbType = 之后的 typename 是不必要的 - 它已被删除。

  • args... 应该被完美地转发到fooImpl 以保留其值类别foofooImpl 都应该将args... 作为转发参考

wandbox example


请注意,有一个提案可以更轻松地处理非终端参数包:P0478R0 - "Template argument deduction for non-terminal function parameter packs"。这将使您的原始实现按预期工作。

【讨论】:

  • 所以不值得。 +1。我建议将std::function 放在首位, 将函数调用分解为多个调用。
  • @Yakk std::function 必须是最后一个。不是因为“异步函数必须有一个回调作为它们的最后一个参数”(我从来没有说过),而是因为它是这个特定任务的一个条件。
  • Vittorio,感谢您对type_wrapper 的提示。我想我会这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-10
相关资源
最近更新 更多