Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

В компьютерном программировании , VARIADIC шаблоны являются шаблонами , которые принимают переменное число аргументов.

VARIADIC шаблоны поддерживаются C ++ (так как C ++ 11 стандарта), и язык программирования D .

C ++ [ править ]

Возможность вариативного шаблона C ++ была разработана Дугласом Грегором и Яакко Ярви [1] [2] и позже была стандартизирована в C ++ 11. До C ++ 11 шаблоны (классы и функции) могли принимать только фиксированное количество аргументов, которые нужно было указывать при первом объявлении шаблона. C ++ 11 позволяет определениям шаблонов принимать произвольное количество аргументов любого типа.

template < typename ...  Values >  class  tuple ;  // принимает ноль или более аргументов

Вышеупомянутый класс шаблона tupleбудет принимать любое количество имен типов в качестве параметров шаблона. Здесь экземпляр вышеуказанного шаблонного класса создается с тремя аргументами типа:

кортеж < int ,  std :: vector < int > ,  std :: map < std :: string ,  std :: vector < int >>>  some_instance_name ;

Количество аргументов может быть равно нулю, так что тоже будет работать.tuple<> some_instance_name;

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

template < typename  First ,  typename ...  Rest >  class  tuple ;  // принимает один или несколько аргументов

Шаблоны с переменным числом аргументов также могут применяться к функциям, тем самым не только обеспечивая безопасное для типов надстройку к функциям с переменным числом аргументов (например, printf), но также позволяя функции, вызываемой с синтаксисом, подобным printf, обрабатывать нетривиальные объекты.

template < typename ...  Params >  void  printf ( const  std :: string  & str_format ,  Params ...  parameters );

Оператор многоточия (...) выполняет две роли. Когда он встречается слева от имени параметра, он объявляет пакет параметров. Используя пакет параметров, пользователь может привязать ноль или более аргументов к параметрам вариативного шаблона. Пакеты параметров также могут использоваться для параметров, не являющихся типами. Напротив, когда оператор с многоточием встречается справа от шаблона или аргумента вызова функции, он распаковывает пакеты параметров в отдельные аргументы, как args...в теле printfниже. На практике использование оператора с многоточием в коде заставляет все выражение, предшествующее многоточию, повторяться для каждого последующего аргумента, распакованного из пакета аргументов, с выражениями, разделенными запятыми.

Использование вариативных шаблонов часто бывает рекурсивным. Сами вариативные параметры не всегда доступны для реализации функции или класса. Следовательно, типичный механизм для определения чего-то вроде вариативной printfзамены C ++ 11 будет следующим:

// базовый случай void  printf ( const  char  * s ) {  while  ( * s )  {  if  ( * s  ==  '%' )  {  if  ( * ( s  +  1 )  ! =  '%' )  ++ s ;  иначе  throw  std :: runtime_error ( «недопустимая строка формата: отсутствуют аргументы» );  } std :: cout  <<  * s ++ ;  } }// рекурсивный шаблон < typename  T ,  typename ...  Args > void  printf ( const  char  * s ,  T  value ,  Args ...  args ) {  while  ( * s )  {  if  ( * s  ==  '%' )  {  if  ( * ( s  +  1 )  ! =  '%' )  {  std :: cout <<  значение ;  s  + =  2 ;  // работает только со строками двухсимвольного формата (% d,% f и т. д.); не выполняется с% 5.4f  printf ( s ,  args ...);  // вызывается, даже если * s равно 0, но в этом случае ничего не делает (и игнорирует лишние аргументы)  return ;  } ++ с ;  } std :: cout  <<  * s ++ ;  }  }

Это рекурсивный шаблон. Обратите внимание, что версия вариативного шаблона printfвызывает саму себя или (в случае, если args...она пуста) вызывает базовый вариант.

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

template < typename ...  Args >  inline  void  pass ( Args && ...)  {}

который можно использовать следующим образом:

 шаблон < typename ...  Args >  inline  void  expand ( Args && ...  args )  {  pass (  some_function ( args ) ...  );  } expand ( 42 ,  "ответ" ,  истина );

который расширится до чего-то вроде:

 передать (  some_function ( arg1 ),  some_function ( arg2 ),  some_function ( arg3 ) и  т.д ...  );

Использование этой функции «проход» необходимо, поскольку расширение пакета аргументов происходит путем разделения аргументов вызова функции запятыми, которые не эквивалентны оператору запятой. Поэтому some_function(args)...;никогда не получится. Более того, приведенное выше решение будет работать только в том случае, если тип возврата some_function- нет void. Кроме того, some_functionвызовы будут выполняться в неопределенном порядке, поскольку порядок оценки аргументов функции не определен. Чтобы избежать неопределенного порядка, можно использовать списки инициализаторов, заключенные в фигурные скобки, что гарантирует строгий порядок оценки слева направо. Для списка инициализаторов требуется voidтип, не возвращаемый, но оператор запятой может использоваться для вывода 1для каждого элемента раскрытия.

 struct  pass  {  template < typename  ... T >  pass ( T ...)  {}  }; передать {( some_function ( args ),  1 ) ...};

Вместо выполнения функции можно указать и выполнить лямбда-выражение на месте, что позволяет выполнять произвольные последовательности операторов на месте.

 передать {([&] () {std :: cout << args << std :: endl;} (), 1) ...};

Однако в этом конкретном примере лямбда-функция не нужна. Вместо этого можно использовать более обычное выражение:

 передать {(std :: cout << args << std :: endl, 1) ...};

Другой способ - использовать перегрузку с «версиями завершения» функций. Это более универсальный вариант, но для его создания требуется немного больше кода и больше усилий. Одна функция получает один аргумент некоторого типа и пакет аргументов, а другая не получает ни одного. (Если бы у обоих был один и тот же список начальных параметров, вызов был бы неоднозначным - один только пакет переменных параметров не может устранить неоднозначность вызова.) Например:

void  func ()  {}  // версия завершенияшаблон < typename  Arg1 ,  typename ...  Args > void  func ( const  Arg1 &  arg1 ,  const  Args && ...  args ) {  process (  arg1  );  func ( аргументы ...);  // примечание: arg1 здесь не отображается! }

Если args...содержит хотя бы один аргумент, он будет перенаправлен на вторую версию - пакет параметров может быть пустым, и в этом случае он просто перенаправит на завершающую версию, которая ничего не сделает.

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

шаблон  < typename ...  BaseClasses > class  ClassName  :  public  BaseClasses ... { public :  ClassName  ( BaseClasses && ...  base_classes )  :  BaseClasses ( base_classes ) ...  {} };

Оператор распаковки будет реплицировать типы для базовых классов ClassName, так что этот класс будет производным от каждого из переданных типов. Кроме того, конструктор должен принимать ссылку на каждый базовый класс, чтобы инициализировать базовые классы ClassName.

Что касается шаблонов функций, можно пересылать переменные параметры. В сочетании с универсальными ссылками (см. Выше) это позволяет безупречно пересылать:

шаблон < имяТипа  TypeToConstruct > структура  SharedPtrAllocator {  шаблон < имяТипа  ... Args >  станд :: shared_ptr < TypeToConstruct >  construct_with_shared_ptr ( Args && ...  PARAMS )  {  возвращение  станд :: shared_ptr < TypeToConstruct > ( новый  TypeToConstruct ( станд :: вперед < Args > ( params) ...));  } };

Это распаковывает список аргументов в конструктор TypeToConstruct. std::forward<Args>(params)Синтаксис совершенно перенаправляет аргументы , как их собственных типов, даже в отношении RValue-Несс, в конструктор. Оператор распаковки распространит синтаксис пересылки на каждый параметр. Эта конкретная фабричная функция автоматически обертывает выделенную память в std::shared_ptrцелях безопасности в отношении утечек памяти.

Кроме того, количество аргументов в пакете параметров шаблона можно определить следующим образом:

шаблон < typename  ... Args > struct  SomeStruct {  static  const  int  size  =  sizeof ... ( Args ); };

Выражение SomeStruct<Type1, Type2>::sizeдаст 2, а SomeStruct<>::sizeдаст 0.

D [ править ]

Определение [ править ]

Определение вариативных шаблонов в D аналогично их аналогу в C ++:

template  VariadicTemplate ( Args ...)  {  / * Body * /  }

Точно так же любой аргумент может предшествовать списку аргументов:

template  VariadicTemplate ( T ,  строковое  значение ,  символ псевдонима  , Args ...) { / * Body * / }    

Основное использование [ править ]

Вариативные аргументы очень похожи на постоянный массив в их использовании. Они могут быть повторены, доступны по индексу, имеют lengthсвойство и могут быть нарезаны . Операции интерпретируются во время компиляции, что означает, что операнды не могут быть значениями времени выполнения (такими как параметры функции).

Все, что известно во время компиляции, может быть передано как переменные аргументы. Он делает вариативные аргументы похожими на аргументы псевдонима шаблона , но более мощными, поскольку они также принимают базовые типы (char, short, int ...).

Вот пример, который выводит на печать строковое представление переменных параметров. StringOfи StringOf2дают одинаковые результаты.

статический  int  s_int ;struct  Dummy  {}void  main () {  pragma ( msg ,  StringOf ! ( "Привет, мир" ,  uint ,  Dummy ,  42 ,  s_int ));  pragma ( msg ,  StringOf2 ! ( «Привет, мир» ,  uint ,  Dummy ,  42 ,  s_int )); }шаблон  StringOf ( Args ...) {  перечисление  StringOf  =  Args [ 0 ]. stringof  ~  StringOf ! ( Args [ 1 .. $]); }шаблон  StringOf () {  перечисление  StringOf  =  "" ; }Шаблон  StringOf2 ( Args ...) {  статический ,  если  ( Args . длина  ==  0 )  перечисление  StringOf2  =  "" ;  иначе  перечисление  StringOf2  =  Args [ 0 ]. stringof  ~  StringOf2 ! ( Args [ 1 .. $]); }

Выходы:

"Привет, мир" uintDummy42s_int"Привет, мир" uintDummy42s_int

AliasSeq [ править ]

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

псевдоним  AliasSeq ( Args ...)  =  Args ;

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

импорт  std . мета ;void  main () {  // Примечание: AliasSeq не может быть изменен, а псевдоним не может быть повторно привязан, поэтому нам нужно определить новые имена для наших изменений.  номера псевдонимов  = AliasSeq ! ( 1 , 2 , 3 , 4 , 5 , 6 ); // Псевдоним нарезки lastHalf = numbers [$ / 2 .. $]; статическое утверждение ( lastHalf == AliasSeq ! ( 4 , 5 , 6 ));                       // AliasSeq автоматическое расширение  псевдонима  digits  =  AliasSeq ! ( 0 ,  числа ,  7 ,  8 ,  9 );  статическое  утверждение ( цифры  ==  AliasSeq ! ( 0 ,  1 ,  2 ,  3 ,  4 ,  5 ,  6 ,  7 ,  8 ,  9 ));  // std.meta предоставляет шаблоны для работы с AliasSeq, такие как anySatisfy, allSatisfy, staticMap и Filter.  псевдоним  evenNumbers  = Фильтр ! ( IsEven ,  цифры );  статическое  утверждение ( evenNumbers  ==  AliasSeq ! ( 0 ,  2 ,  4 ,  6 ,  8 )); }Шаблон  ISeven ( INT  номер ) {  перечисление  ISeven  =  ( 0  ==  ( номер  %  2 )); }

См. Также [ править ]

Для статей о вариативных конструкциях, отличных от шаблонов

Ссылки [ править ]

  1. Дуглас Грегор и Яакко Ярви (февраль 2008 г.). «Вариативные шаблоны для C ++ 0x» . Журнал объектных технологий . С. 31–51.
  2. Дуглас Грегор; Яакко Ярви и Гэри Пауэлл. (Февраль 2004 г.). «Вариативные шаблоны. Номер N1603 = 04-0043 в предиднейской рассылке Комитета по стандартизации ISO C ++».

Внешние ссылки [ править ]