可变参数模板调度程序

Posted

技术标签:

【中文标题】可变参数模板调度程序【英文标题】:Variadic Template Dispatcher 【发布时间】:2014-11-04 13:31:00 【问题描述】:

我想使用可变参数模板来帮助解决使用 va-args 的问题。基本上,我想调用一个单数函数,将“命令”连同参数的变量列表传递给函数,然后将参数分派给另一个函数。

我已经使用经过验证的真实(但不是类型安全)va_list 实现了这一点。这是我使用可变参数模板进行的尝试。下面的示例无法编译,因为您很快就会发现原因...

#include <iostream>

using namespace std;
typedef enum cmd_t

    CMD_ZERO,
    CMD_ONE,
    CMD_TWO,
 COMMANDS;


int cmd0(double a, double b, double c)

    cout << "cmd0  " << a << ", " << b << ", " << c << endl;
    return 0;


int cmd1(int a, int b, int c)

    cout << "cmd1  " << a << ", " << b << ", " << c << endl;
    return 1;


template<typename... Args>
int DispatchCommand(COMMANDS cmd, Args... args)

    int stat = 0;
    switch (cmd)
    
    case CMD_ZERO:
        cmd0(args...);
        break;
    case CMD_ONE:
        cmd1(args...);
        break;
    default:
        stat = -1;
        break;
    
    return stat;


int main()

    int stat;
    stat = DispatchCommand(CMD_ZERO, 1, 3.141, 4);
    stat = DispatchCommand(CMD_ONE, 5, 6, 7);
    stat = DispatchCommand(CMD_TWO, 5, 6, 7, 8, 9);

    system("pause");
    return 0;

有人知道如何修改此函数以正确使用可变参数模板吗?

【问题讨论】:

为什么不使用普通的旧函数重载?定义一个函数DispatchCommand,它接受4个参数,重载一个接受5个参数的函数,以此类推。 在编译时是否知道COMMANDS cmd 也许这会有所帮助:***.com/a/25264850 @Jarod42:必须提供正确的参数类型。因此,这个问题毫无意义。他试图“忘记”他将要使用哪个函数,然后再次推演,却无法正确推演。解决方案是首先不要忘记签名。 @Jarod40 - 是的,COMMANDS 枚举在编译时是已知的 【参考方案1】:

编写一些代码,给定一个函数指针和一组参数,使用这些参数中最长的前缀调用它。

template<class...>struct typesusing type=types;;
template<class types0, size_t N, class types1=types<>>
struct split;

template<class t00, class...t0s, size_t N, class...t1s>
struct split<types<t00,t0s...>,N,types<t1s...>>:
  split<types<t0s...>,N-1,types<t1s...,t00>>
;
template<class...t0s, class...t1s>
struct split<types<t0s...>,0,types<t1s...>>

  using right=types<t0s...>;
  using left=types<t1s...>;
;
template<class>using void_t=void;
template<class Sig,class=void>
struct valid_call:std::false_type;
template<class F, class...Args>
struct valid_call<F(Args...), void_t<
  decltype( std::declval<F>()(std::declval<Args>()...) )
>>:std::true_type ;

template<class R, class types>
struct prefix_call;

template<class R, class...Args>
struct prefix_call<R, types<Args...>> 
  template<class F, class... Extra>
  std::enable_if_t< valid_call<F(Args...)>::value, R >
  operator()(R default, F f, Args&&...args, Extra&&...) const
  
    return std::forward<F>(f)(args...);
  
  template<class F, class... Extra>
  std::enable_if_t< !valid_call<F(Args...)>::value, R >
  operator()(R default, F f, Args&&...args, Extra&&...) const
  
    return prefix_call<R, typename split<types<Args...>, sizeof...(Args)-1>::left>(
      std::forward<R>(default), std::forward<F>(f), std::forward<Args>(args)...
    );
  
;

template<class R>
struct prefix_call<R, types<>> 
  template<class F, class... Extra>
  std::enable_if_t< valid_call<F()>::value, R >
  operator()(R default, F f, Extra&&...) const
  
    return std::forward<F>(f)();
  
  template<class F, class... Extra>
  std::enable_if_t< !valid_call<F()>::value, R >
  operator()(R default, F f, Extra&&...) const
  
    return std::forward<R>(default);
  
;

以上代码可能有错别字。

template<typename... Args>
int DispatchCommand(COMMANDS cmd, Args... args)

  int stat = 0;
  switch (cmd) 
    case CMD_ZERO: 
      stat = prefix_call<int, Args...>(-1, cmd0, std::forward<Args>(args)...);
     break;
    case CMD_ONE: 
      stat = prefix_call<int, Args...>(-1, cmd1, std::forward<Args>(args)...);
     break;
    default: 
      stat = -1;
     break;
  
  return stat;

如果cmd0cmd1 被覆盖,则必须使用重载集技术。

【讨论】:

【参考方案2】:

您可以使用以下内容:

template <COMMANDS cmd> struct command

    template <typename ... Args>
    int operator() (Args&&...) const  return -1; 
;

template <> struct command<CMD_ZERO>

    int operator()(double a, double b, double c) const
    
        std::cout << "cmd0  " << a << ", " << b << ", " << c << std::endl;
        return 0;
    
;

template <> struct command<CMD_ONE>

    int operator()(int a, int b, int c) const
    
        std::cout << "cmd1  " << a << ", " << b << ", " << c << std::endl;
        return 1;
    
;

template <COMMANDS cmd, typename... Args> int DispatchCommand(Args&&... args)

    return command<cmd>()(std::forward<Args>(args)...);

然后像这样使用它:

DispatchCommand<CMD_ZERO>(1., 3.141, 4.);
DispatchCommand<CMD_ONE>(5, 6, 7);
DispatchCommand<CMD_TWO>(5, 6, 7, 8, 9);

Live example

但直接使用不同的函数似乎更简单:

cmd0(1., 3.141, 4.);
cmd1(5, 6, 7);

【讨论】:

以上是关于可变参数模板调度程序的主要内容,如果未能解决你的问题,请参考以下文章

[C++11 模板的改进] --- 可变参数模板

可变参数模板类:是不是可以为每个可变参数模板参数实现一个唯一的成员函数?

为啥可变参数模板的模板特化与非可变模板的特化不同?

C++11 ——— 可变参数模板

C++11 ——— 可变参数模板

使用可变参数模板函数围绕类实现基于 pImpl 的包装器