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

时间:2017-02-19 22:34:11

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

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

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

此处CbTypeInArgsstd::function的最后一个类型。然后将临时的CbType传递给fooImpl(),其中推导出CbArgs。这有效,但看起来很难看。

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

1 个答案:

答案 0 :(得分:3)

  

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

参数包不是最后一个参数时,无法推断。告诉编译器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之后的typename CbType =是不必要的 - 它已被删除。

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

wandbox example

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