【问题标题】:What's the best way to have a variable number of template parameters?拥有可变数量的模板参数的最佳方法是什么?
【发布时间】:2010-06-27 01:54:24
【问题描述】:

请考虑这个-可能写得不好-示例:

class Command;

 class Command : public  boost::enable_shared_from_this<Command>
 { 
  public :
   void execute()
   { 
    executeImpl();
                // then do some stuff which is common to all commands ... 
   }

   // Much more stuff ...
     private:
      virtual void executeImpl()=0;
         // Much more stuff too ...
 };

和:

class CmdAdd : public Command
 {
 public:
  CmdAdd(int howMuchToAdd);
  void executeImpl();


  int _amountToAdd;
 };

// implementation isn't really important here .... 

有了这个,我可以简单地使用这个语法添加一个回调:

        boost::shared_ptr<Command> cmdAdd(CmdAdd(someValue));
     cmdAdd->execute();

它完美无缺。我的“命令”类做了更多所有命令共有的事情,例如实现撤消、重做、进度报告等,但为了便于阅读,我将其从代码中删除。

现在我的问题很简单: 有没有办法重写命令类,以便我可以替换这个调用:

boost::shared_ptr<Command> cmdAdd(CmdAdd(someValue));
cmdAdd->execute();

通过类似的东西:

CmdAdd(someValue); // preferably
or CmdAdd->execute(someValue)

我一直在思考这个问题,但我有一个概念上的问题: 我想将我的 Command 类模板化为

template <typename R,typename T1, typename T2, ..., typename Tn> class Command
{
    R1 execute(T1 p1, ...,Tn pn)
    { 
        return executeImpl(T1 p1, ...,Tn pn);
        // then do some stuff which is common to all commands ... 
    }
}

但显然,这里有一个问题: 语法template &lt;typename R,typename T1, typename T2, ..., typename Tn&gt; 是不合法的 C++ ,AFAIK。

我是否必须编写 n 个版本的 Command,例如:

template <typename R> class Command
template <typename R,typename T1> class Command
template <typename R,typename T1, typename T2> class Command
...

等等? (甚至不确定这是否真的有效)

或者还有其他更优雅的方法吗? 那里提到的here 的语法有什么用吗? (函数 f;)

我一直在查看 Loki 的类型列表,它们似乎可以胜任。但是我在Boost中找不到任何东西。我在网上读到 boost::mpl 是一个想要用来实现类型列表的工具,但我对 MPL 文档有点困惑?

对此有何见解? 问候, D.

【问题讨论】:

    标签: c++ templates boost metaprogramming boost-mpl


    【解决方案1】:

    有趣的问题:)

    首先,您忽略了一个问题:您需要所有Command 的通用基类,如果您要使用它们的堆栈(用于撤消/重做),则无法对此类进行模板化。

    因此你被困在:

    class Command
    {
    public:
      void execute(); 
    private:
      virtual void executeImpl() = 0;
    };
    

    我可以理解您希望使用带参数的执行函数,但不要忘记无论如何您都需要保存这些参数以进行撤消/重做操作。通过构造函数获取它们更简单。

    但是,您仍然可以使用模板化方法来实际调用命令:

    template <class Command>
    void execute() { Command cmd; cmd.execute(); }
    
    template <class Command, class T0>
    void execute(T0& arg0) { Command cmd(arg0); cmd.execute(); }
    
    /// ...
    
    int main(int argc, char* argv[])
    {
      execute<MyLittleCommand>("path", 3);
    }
    

    这与您想要的语法很接近。请注意,这里我故意忘记了堆栈,我认为您需要将其传递给execute 方法进行注册(一旦完成)。

    并不是说我也可能会更改 Command 设计以更接近策略模式:

    struct CommandImpl
    {
      virtual ~CommandImpl();
      virtual void executeImpl() = 0;
    };
    
    class Command
    {
    public:
      template <class C>
      static Command Make() { return Command(new C()); }
    
      template <class C, class T0>
      static Command Make(T0& arg0) { return Command(new C(arg0)); }
    
      /// ....
    
      void execute(CommandStack& stack)
      {
        mImpl->executeImpl();
        stack.Push(*this);
      }
    
    private:
      Command(CommandImpl* c): mImpl(c) {}
      boost::shared_ptr<CommandImpl> mImpl;
    };
    

    它是非虚拟接口和指向实现习惯用法的典型组合。

    【讨论】:

    • 非常有兴趣!事实上,我添加了一个非模板 CommandBase 类,正是为了容器的目的。保存/恢复参数也可以完美运行(取消一些技巧)。我最终选择使用这种方式“template class Command template class Command template class Command”,连同boost预处理器。似乎是最简单的方法,但我仍然遇到一些语法问题。我发现你的方法比我的优雅得多,所以非常感谢,我会看看这个策略模式:-)
    【解决方案2】:

    AFAIK 你不能用当前的 C++ 标准来做到这一点。一些 boost 代码使用宏和其他预处理来模拟可变参数模板(我认为 boost::pool 或 boost::object_pool 使用类似的东西)。

    但是,variadic templates 将出现在下一个标准 C++0x 中,根据此页面,GCC 已经提供了从 v4.3 开始的实现:http://gcc.gnu.org/projects/cxx0x.html

    如果你正在使用它,你可以通过激活 C++0x 来启用它。

    【讨论】:

    • +1 这是真正的答案。然而,考虑到这个新的 C++ 尚未得到所有编译器的支持这一事实:您希望代码的可移植性如何?它是否需要在大多数编译器下都可以编译,或者单个特定的编译器对你来说足够了吗?
    【解决方案3】:

    乍一看,可变参数模板似乎是完美的解决方案。不幸的是,它们不能很好地处理虚函数:

    template <typename... Args>
    void execute(Args&&... args)
    {
        executeImpl(std::forward<Args>(args)...);
    }
    

    这要求executeImpl是virtual成员函数模板,但是C++中没有这个东西!

    【讨论】:

    • +1 表示收获。但他展示的方式仍然有效。喜欢template&lt;typename FType&gt; class Command; template&lt;typename R, typename ...P&gt; struct Command&lt;R(P...)&gt; { /* ... */ virtual R executeImpl(P...) = 0; };那么他可以通过class CmdAdd : public Command&lt;void(int)&gt; { void executeImpl(int howMuch) { /* ... */ } };实现它
    • @Johannes:您的解决方案意味着所有子类都使用相同的函数签名,对吧?这是迪奈兹想要的吗?我不确定。
    • 我同意,只是没有所有命令通用的基类 :)
    • @Matthieu 说得好。 @Fred 他们将他们的函数类型传递给Command 基类,所以他们每个人都可以有不同的函数类型。每个子类的签名(关于其函数类型)不一定相同。这是对提问者模板代码的直接翻译——但我​​没有考虑@Matthieu 关于通用非模板基类的观点。
    【解决方案4】:

    正如 Klaim 所指出的,可变参数模板是这个问题的最终解决方案。但是,有一种方法可以使用类型列表允许可变数量的模板参数:

    template <class H, class T>
    struct typelist
    {
        typedef H head;
        typedef T tail;
    };
    

    这允许你写typelist&lt;typelist&lt;int, float&gt;, double&gt;,例如。然而,读写确实让人头疼,这也是 boost::function 使用蛮力方法的主要原因(每个模板参数的单独类): boost::function0, boost::function1 , boost::function2 等用于其后端实现。这比通过模板元编程递归遍历类型列表要容易得多。

    至于一般性答案,我将其发布在您最初遇到此问题的另一个线程中。

    【讨论】:

    • 我想知道通过使用部分模板特化,是否可以使用 boost 系统,但只有一个函数名,即,而不是 : function0 function1 have function function 并且编译器会根据您提供的模板参数的数量选择正确的版本: MyFunction : public Function -> 会“选择” function1 如果我明白你的意思,应该写 MyFunction : public Function1 让它工作,对吧?
    猜你喜欢
    • 2015-08-04
    • 2012-08-07
    • 2016-12-01
    • 2021-07-06
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 2014-02-28
    • 2016-05-08
    相关资源
    最近更新 更多