如何推断可变参数之后的模板参数?
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_type
或type_is_functor_that_takes_these_arguments
特征类吗? (即,您是否关心未能传入函子是否会导致“找不到有效函数”或模板实例化错误?)
如果不是函子,对f(3)
的调用不会产生错误吗?
是的。但是这个错误会在模板实例化后发生。实际上可能(但更复杂)要求Args&&... 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; );
【讨论】:
以上是关于如何推断可变参数之后的模板参数?的主要内容,如果未能解决你的问题,请参考以下文章