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

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

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

C ++

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

шаблон<typename... Значения> учебный класс кортеж;               // принимает ноль или более аргументов

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

кортеж<int, стандартное::вектор<int>, стандартное::карта<стандартное::нить, стандартное::вектор<int>>> some_instance_name;

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

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

шаблон<typename Первый, typename... Отдых> учебный класс кортеж; // принимает один или несколько аргументов

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

шаблон<typename... Params> пустота printf(const стандартное::нить &str_format, Params... параметры);

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

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

// базовый вариантпустота printf(const char *s){    пока (*s)    {        если (*s == '%')        {            если (*(s + 1) != '%')                ++s;            еще                бросать стандартное::ошибка выполнения("недопустимая строка формата: отсутствуют аргументы");        }        стандартное::cout << *s++;    }}// рекурсивныйшаблон<typename Т, typename... Args>пустота printf(const char *s, Т ценить, Args... аргументы){    пока (*s)    {        если (*s == '%')        {            если (*(s + 1) != '%')            {                стандартное::cout << ценить;                s += 2; // работает только со строками двухсимвольного формата (% d,% f и т. д.); не работает с% 5.4f                printf(s, аргументы...); // вызывается, даже если * s равно 0, но в этом случае ничего не делает (и игнорирует лишние аргументы)                возвращаться;            }            ++s;        }        стандартное::cout << *s++;    }    }

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

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

шаблон<typename... Args> в соответствии пустота проходить(Args&&...) {}

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

  шаблон<typename... Args> в соответствии пустота расширять(Args&&... аргументы)  {    проходить( some_function(аргументы)... );  }  расширять(42, "отвечать", истинный);

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

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

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

  структура проходить  {    шаблон<typename ...Т> проходить(Т...) {}  };  проходить{(some_function(аргументы), 1)...};

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

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

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

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

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

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

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

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

шаблон <typename... Базовые классы>учебный класс ClassName : общественный Базовые классы...{общественный:    ClassName (Базовые классы&&... base_classes)        : Базовые классы(base_classes)...    {}};

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

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

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

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

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

шаблон<typename ...Args>структура SomeStruct{    статический const int размер = размер...(Args);};

Выражение SomeStruct <Тип1, Тип2> :: размер даст 2, а SomeStruct <> :: размер даст 0.

D

Определение

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

шаблон VariadicTemplate(Args...) { /* Тело */ }

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

шаблон VariadicTemplate(Т, нить ценить, псевдоним символ, Args...) { /* Тело */ }

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

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

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

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

статический int s_int;структура Дурачок {}пустота главный(){  прагма(сообщение, StringOf!("Привет, мир", uint, Дурачок, 42, s_int));  прагма(сообщение, StringOf2!("Привет, мир", uint, Дурачок, 42, s_int));}шаблон StringOf(Args...){  перечислить StringOf = Args[0].строка ~ StringOf!(Args[1..$]);}шаблон StringOf(){  перечислить StringOf = "";}шаблон StringOf2(Args...){  статический если (Args.длина == 0)    перечислить StringOf2 = "";  еще    перечислить StringOf2 = Args[0].строка ~ StringOf2!(Args[1..$]);}

Выходы:

«Привет, мир» uintDummy42s_int «Привет, мир» uintDummy42s_int

AliasSeq

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

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

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

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

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

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

Рекомендации

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

внешняя ссылка