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

时间:2013-04-12 17:43:45

标签: c++ templates c++11 variadic

我有以下模板功能:

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可以是除函数指针之外的任何类型,如何解决这个问题。

3 个答案:

答案 0 :(得分:8)

从14.1p11开始:

  

不得遵循功能模板的模板参数包   除非可以从parameter-type-list推导出该模板参数,否则使用另一个模板参数   函数模板或具有默认参数(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)...));
}

答案 1 :(得分:4)

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 {}

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

然后您使用以下内容扩充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 */ }

这是非常迟钝的。

答案 2 :(得分:0)

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

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