Каков наилучший способ иметь переменное количество параметров шаблона? - PullRequest
2 голосов
/ 27 июня 2010

Пожалуйста, примите во внимание это, вероятно, плохо написанный пример:

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

Работает без нареканий. Мой класс «Command» делает гораздо больше вещей, которые являются общими для всех команд, таких как реализация отмены, возврата, отчета о ходе выполнения и т. Д., Но я удалил его из кода для удобства чтения.

Теперь мой вопрос прост: Есть ли способ переписать командный класс, чтобы я мог заменить этот вызов:

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 <typename R,typename T1, typename T2, ..., typename Tn> не является допустимым 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
...

и так далее? (даже не уверен, что это сработает)

Или есть другой, более элегантный способ сделать это? Синтаксис, упомянутый здесь какого-либо использования там? (функция f;)

Я просматривал списки типов Локи, и они, похоже, справляются со своей задачей. Но я не могу найти ничего такого в Boost. Я читал в Интернете, что boost :: mpl - это то, что нужно использовать для реализации списков типов, но меня немного смущают документы MPL?

Есть идеи по этому поводу? Regads, D.

Ответы [ 4 ]

2 голосов
/ 27 июня 2010

Интересный вопрос:)

Прежде всего, есть проблема, которую вы пропустили: вам нужен общий базовый класс для всех 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;
};

Это типичное сочетание не виртуального интерфейса и идиом указателя на реализацию.

2 голосов
/ 27 июня 2010

На первый взгляд, вариационные шаблоны кажутся идеальным решением.К сожалению, они не очень хорошо работают с виртуальными функциями:

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

Для этого требуется executeImpl, чтобы быть виртуальной функцией-членом template , но неттакая вещь в C ++!

2 голосов
/ 27 июня 2010

AFAIK Вы не можете сделать это с текущим стандартом C ++.В некотором буст-коде используются макросы и другая предварительная обработка для имитации шаблонов с переменными параметрами (я думаю, что boost :: pool или boost :: object_pool используют что-то подобное).следующий стандарт C ++ 0x и согласно этой странице GCC уже предоставляет реализацию, начиная с v4.3: http://gcc.gnu.org/projects/cxx0x.html

Если вы используете его, вы можете включить его, активировав C ++ 0x.

0 голосов
/ 27 июня 2010

Шаблоны Variadic, как указал Клаим, являются окончательным решением этой проблемы. Однако есть способ разрешить переменное число аргументов шаблона, используя списки типов:

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

Это позволяет вам написать, например, typelist<typelist<int, float>, double>. Однако, это настоящая боль в шее для чтения и записи, и это главная причина, почему boost :: function использует подход грубой силы (отдельный класс для каждого числа аргументов шаблона): boost :: function0, boost :: function1 , boost :: function2 и т. д. для его серверной реализации. Это намного проще, чем рекурсивный обход списков типов с помощью шаблонного метапрограммирования.

Что касается общего ответа, я разместил его в другой ветке, где у вас изначально был этот вопрос вместе с другим.

...