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

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

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

C ++

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

template<typename... Values> class tuple;               // takes zero or more arguments

Приведенный выше класс шаблона 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; // takes one or more arguments

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

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

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

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

// base case
void my_printf(const char *s)
{
    while (*s)
    {
        if (*s == '%')
        {
            if (*(s + 1) != '%')
                ++s;
            else
                throw std::runtime_error("invalid format string: missing arguments");
        }

        std::cout << *s++;
    }
}

// recursive
template<typename T, typename... Args>
void my_printf(const char *s, T value, Args... args)
{
    while (*s)
    {
        if (*s == '%')
        {
            if (*(s + 1) != '%')
            {
                // pretend to parse the format: only works on 2-character format strings ( %d, %f, etc ); fails with %5.4f
                s += 2;
                // print the value
                std::cout << value;
                // called even when *s is 0 but does nothing in that case (and ignores extra arguments)
                my_printf(s, args...);
                return;
            }

            ++s;
        }

        std::cout << *s++;
    }    
}

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

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

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

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

template<typename... Args> inline void expand(Args&&... args)
{
    pass(some_function(args)...);
}

expand(42, "answer", true);

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

    pass(some_function(arg1), some_function(arg2), some_function(arg3) /* etc... */ );

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

struct pass
{
    template<typename ...T> pass(T...) {}
};

pass{(some_function(args), 1)...};

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

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

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

    pass{(std::cout << args << std::endl, 1)...};

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

void func() {} // termination version

template<typename Arg1, typename... Args>
void func(const Arg1& arg1, const Args&&... args)
{
    process( arg1 );
    func(args...); // note: arg1 does not appear here!
}

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

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

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-Несс, в конструктор. Оператор распаковки распространит синтаксис пересылки на каждый параметр. Эта конкретная фабричная функция автоматически обертывает выделенную память в std::shared_ptrдля некоторой степени безопасности в отношении утечек памяти.

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

template<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, string value, alias symbol, Args...) { /* Body */ }

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

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

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

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

static int s_int;

struct Dummy {}

void main()
{
  pragma(msg, StringOf!("Hello world", uint, Dummy, 42, s_int));
  pragma(msg, StringOf2!("Hello world", uint, Dummy, 42, s_int));
}

template StringOf(Args...)
{
  enum StringOf = Args[0].stringof ~ StringOf!(Args[1..$]);
}

template StringOf()
{
  enum StringOf = "";
}

template StringOf2(Args...)
{
  static if (Args.length == 0)
    enum StringOf2 = "";
  else
    enum StringOf2 = Args[0].stringof ~ StringOf2!(Args[1..$]);
}

Выходы:

"Hello world"uintDummy42s_int
"Hello world"uintDummy42s_int

AliasSeq

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

alias AliasSeq(Args...) = Args;

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

import std.meta;

void main()
{
  // Note: AliasSeq can't be modified, and an alias can't be rebound, so we'll need to define new names for our modifications.
  alias numbers = AliasSeq!(1, 2, 3, 4, 5, 6);
  // Slicing
  alias lastHalf = numbers[$ / 2 .. $];
  static assert(lastHalf == AliasSeq!(4, 5, 6));
  // AliasSeq auto expansion
  alias digits = AliasSeq!(0, numbers, 7, 8, 9);
  static assert(digits == AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
  // std.meta provides templates to work with AliasSeq, such as anySatisfy, allSatisfy, staticMap, and Filter.
  alias evenNumbers = Filter!(isEven, digits);
  static assert(evenNumbers == AliasSeq!(0, 2, 4, 6, 8));
}

template isEven(int number)
{
  enum isEven = (0 == (number % 2));
}

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

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

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

Внешние ссылки