Вариативный шаблон

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

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

С++

Функция вариативных шаблонов 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 >>> имя_экземпляра ;    

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

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

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

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

template < typename ... Params > void my_printf ( const std :: string & str_format , Params ... параметры );       

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

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

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

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

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

шаблон < typename ... Args > inline void pass ( Args && ...) {}     

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

шаблон < typename ... Args > inline void expand ( Args && ... args ) { pass ( some_function ( args )...); }      развернуть ( 42 , "ответ" , правда );  

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

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

Использование этой функции "pass" необходимо, поскольку расширение пакета аргументов происходит путем разделения аргументов вызова функции запятыми, которые не эквивалентны оператору запятой. Следовательно, 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)...};

В C++17 их можно переписать, используя выражения свёртки для оператора запятой:

 ([&](){ std::cout << args << std::endl; }(), ...);  ((std::cout << args << std::endl), ...);

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

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

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

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

template < typename ... BaseClasses > class ClassName : public BaseClasses ... { public : ClassName ( BaseClasses && ... base_classes ) : BaseClasses ( base_classes )... {} };            

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

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

template < typename TypeToConstruct > struct SharedPtrAllocator { template < typename ... Args > std :: shared_ptr < TypeToConstruct > construct_with_shared_ptr ( Args && ... params ) { return std :: shared_ptr < TypeToConstruct > ( new TypeToConstruct ( std :: forward < Args > ( params )...)); } };            

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

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

template < typename ... Args > struct SomeStruct { static const int size = sizeof ...( Args ); };        

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

Д

Определение

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

template VariadicTemplate ( Args ...) { /* Тело */ }    

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

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

Основное использование

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

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

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

статическое целое s_int ;  структура Dummy {}  void main () { pragma ( msg , StringOf !( "Привет, мир" , uint , Dummy , 42 , s_int )); pragma ( msg , StringOf2 !( "Привет, мир" , uint , Dummy , 42 , s_int )); }             template StringOf ( Args ...) { enum StringOf = Args [ 0 ]. stringof ~ StringOf !( Args [ 1. .$]); }       шаблон StringOf () { enum StringOf = "" ; }     template StringOf2 ( Args ...) { static if ( Args . length == 0 ) enum StringOf2 = "" ; else enum StringOf2 = Args [ 0 ]. stringof ~ StringOf2 !( Args [ 1. . $]); }                 

Выходы:

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

AliasSeq

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

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

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

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

Смотрите также

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

Ссылки

  1. ^ Дуглас Грегор и Яакко Ярви (февраль 2008 г.). «Шаблоны с переменным числом аргументов для C++0x». Журнал объектных технологий . С.  31–51 .
  2. ^ Дуглас Грегор; Яакко Ярви и Гэри Пауэлл. (Февраль 2004 г.). «Шаблоны с переменным числом аргументов. Номер N1603=04-0043 в предсиднейской рассылке Комитета по стандартизации ISO C++».
  • Рабочий проект для языка C++, 16 января 2012 г.
  • Вариативные шаблоны на языке D
Взято с "https://en.wikipedia.org/w/index.php?title=Variadic_template&oldid=1270414780"