如何推断可变参数之后的模板参数?

Posted

技术标签:

【中文标题】如何推断可变参数之后的模板参数?【英文标题】:How can a template argument after variardic arguments be inferred? 【发布时间】:2013-04-12 17:43:44 【问题描述】:

我有以下模板函数:

template <typename...Args, typename Func>
void call(const char *name, Args...args, Func f)

        f(3);

当我尝试使用它时,比如

    call("test", 1, 2, 3, [=](int i)  std::cout<< i; );

编译器抱怨它无法推断模板参数Func。 这个问题怎么解决,知道args可以是除函数指针以外的任何类型。

【问题讨论】:

你能重新排序参数吗? name参数是方法,args是方法的参数,func是回调函数。因此,以这种方式,顺序看起来很“自然”。所以,我宁愿不改变参数的顺序。 否则,语言规则是参数包必须是最后一个参数。 【参考方案1】:

从 14.1p11 开始:

不应遵循函数模板的模板参数包 由另一个模板参数,除非该模板参数可以从参数类型列表中推导出来 函数模板的名称或具有默认参数 (14.8.2)。

如果你想保持 callable 作为最后一个参数,你可以使用forward_as_tuple:

template <typename...Args, typename Func>
void call(const char *name, std::tuple<Args...> args, Func f)

        f(3);


call("test", std::forward_as_tuple(1, 2, 3), [=](int i)  std::cout<< i; );

我们实际上可以通过合成tuple 来包含可调用对象来做得更好:

#include <tuple>

template<typename... Args_F>
void call_impl(const char *name, std::tuple<Args_F... &&> args_f) 
   auto &&f = std::get<sizeof...(Args_F) - 1>(args_f);
   f(3);


template<typename...ArgsF>
void call(const char *name, ArgsF &&...args_f) 
   call_impl(name, std::tuple<ArgsF &&...>(std::forward<ArgsF>(args_f)...));

【讨论】:

这也是一个很好的解决方案。我绝对想从外面隐藏 std::tuple ,所以合成的解决方案很好。 +1!【参考方案2】:

get_last,提取参数包的最后一个元素。

叫它f。致电f

举个例子,

template<typename T0>
auto get_last( T0&& t0 )->decltype(std::forward<T0>(t0))

  return std::forward<T0>(t0);

template<typename T0, typename... Ts>
auto get_last( T0&& t0, Ts&&... ts )->decltype(get_last(std::forward<Ts>(ts)...))

  return get_last(std::forward<Ts>(ts)...);

如果您不关心重载解析,只需调用 get_last 并将其视为仿函数就足够了:

template <typename...Args>
void call(const char *name, Args...&& args)

    auto&& f = get_last(std::forward<Args>(args)...);
    f(3);

下一步是做一些 SFINAE enable_if 魔术,以使 call 如果您最后没有传递有效的仿函数则无法匹配:但是,这可能是矫枉过正。

要检测f(3) 是否有效,一个简单的特征类:

// trivial traits class:
template<typename T>
struct is_type:std::true_type ;

template<typename Functor, typename=void>
struct can_be_called_with_3:std::false_type 

template<typename Functor>
struct can_be_called_with_3<Functor,
  typename std::enable_if<
    std::is_type< decltype(
      std::declval<Functor>(3)
    ) >::value
  >::type
>:std::true_type 

这很愚蠢。如果您对传入类型的要求更复杂(例如,您希望使用参数调用它),则必须使用更高级的特征类。

然后你将call 扩充为:

template <typename...Args>
auto call(const char *name, Args...&& args)
  -> typename std::enable_if<
       can_be_called_with_3< decltype( get_last(std::forward<Args>(args)... ) ) >::value
     >::type
 /* body unchanged */ 

这是相当迟钝的。

【讨论】:

您能举个例子详细说明一下吗?鉴于我事先不知道参数的类型。 您需要last_typetype_is_functor_that_takes_these_arguments 特征类吗? (即,您是否关心未能传入函子是否会导致“找不到有效函数”或模板实例化错误?) 如果不是函子,对f(3)的调用不会产生错误吗? 是的。但是这个错误会在模板实例化后发生。实际上可能(但更复杂)要求Args&amp;&amp;... args 的最后一个参数是有效的函子,如果不是,则对call 的调用无法匹配重载。我怀疑这在你的情况下是多余的,只有当你有另一个 call 函数不将函子作为最后一个参数时它才真正有用(比如,你想要一个默认函子,所以调用者并不总是有传递一个)。 好吧,看起来 OP 不需要重载。因此,SFINAE 的唯一目的是引发一个错误,指出未找到函数调用(然后,使用 GCC,它会针对每个不可用的重载发出注释)。没有 SFINAE 守卫,只能说 f 没有呼叫操作员。如果你只是使用了 static_assert,你可能会得到一个可读性很强的错误(assertion failed: can_be_call_with_3),然后是 template spew。由于它们都吐了,而且大多数人都知道要出于习惯寻找第一个错误,因此带有 enable_if 的 SFINAE 似乎是矫枉过正(除非 OP 需要重载)。【参考方案3】:

如果你想用一组参数作为模板,你不能用你已经做过的方式来写这个:

template <typename...Args, typename Func>
void call(const char *name, Args...args, Func f)

        f(3);

但您可以将它们打包在std::tuple

template <typename...Args, typename Func>
void call(const char *name, std::tuple<Args...> args, Func f)

        f(3);


call("test", std::forward_as_tuple(1, 2, 3), [=](int i)  std::cout<< i; );

【讨论】:

以上是关于如何推断可变参数之后的模板参数?的主要内容,如果未能解决你的问题,请参考以下文章

如何使通用可变参数函数中先前声明的函数的返回类型成功进行上下文推断?

如何使用可变参数模板参数保存可变数量的参数?

如何将构造函数(可变参数)作为模板参数传递?

如何遍历打包的可变参数模板参数列表?

具有模板函数名称的可变参数模板

如何存储可变参数模板参数?