std::function 参数的模板参数和推导

Template argument and deduction of std::function parameters

假设有一个模板函数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?

的临时实例,是否有更好的解决方案

Why doesn't this compile? Why does the template argument deduction fail?

参数包不是最后一个参数时,无法推导。告诉编译器 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");
}

I wonder if there is a better solution without having two functions and a temporary instance of 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... 应为 perfectly-forwarded 至 fooImpl 以保留其 值类别 foofooImpl 都应该把 args... 作为 forwarding-reference.

wandbox example


请注意,有一项提案可以使 non-terminal 参数包的处理更加容易:P0478R0 - “non-terminal 的模板参数推导” 函数参数包。这将使您的原始实现按预期工作。