Search code examples
c++templatesboostmetaprogrammingboost-mpl

What's the best way to have a variable number of template parameters?


Please consider this -probably poorly written- example :

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

and :

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


  int _amountToAdd;
 };

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

With this, I can simply add a callback using this syntax :

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

It works flawlessly. My "Command" class does much more things which are common to all commands, such as implementing undo, redo, progress report and so on, but I removed it from the code for the sake of readability.

Now my question is simple : is there a way to rewrite the command class, so that I can replace this call :

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

by something like :

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

I've been thinking about that a lot but I have a conceptual problem : I wanted to template my Command class like

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

but obviously, there's a problem here : the syntax template <typename R,typename T1, typename T2, ..., typename Tn> isn't legal C++ , AFAIK.

Do I have to write n versions of Command, like :

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

and so on ? (not even sure this is gonna work indeed)

Or is there another, more elegant way to do this ? Is the syntax, mentioned here of any use there ? (function f;)

I've been looking at Loki's type lists and they seem to do the job. But I can't find anything that in Boost. I read on the web that boost::mpl is what one wants to use to implement typelists, but I'm a bit confused by MPL docs ?

Any insights on this ? Regads, D.


Solution

  • Interesting question :)

    First of all, there is an issue you overlooked: you need a common base class for all Command and this class cannot be templated if you are going to use a stack of them (for undo/redo).

    Therefore you are stuck with:

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

    I can understand your desire an execute function with parameters, but don't forget that anyway you would need to save those parameters for the undo/redo operation. It's just simpler to get them through the constructor.

    However, you could still use a templated method to actually invoke a command:

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

    Which is close to the syntax you desired. Note that I have purposely forgotten about the stack here, in my mind you need to pass it to the execute method for registration (once completed).

    Not that I would also probably change the Command design to get closer to a Strategy pattern:

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

    It's the typical combination of Non Virtual Interface and Pointer to Implementation idioms.