Jump to content

Вариатический шаблон

(Перенаправлено из шаблонов Variadic )

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

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

Функция вариативного шаблона C++ была разработана Дугласом Грегором и Яакко Ярви. [1] [2] и позже был стандартизирован в 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 ниже. На практике использование оператора многоточия в коде приводит к повторению всего выражения, предшествующего многоточию, для каждого последующего аргумента, распакованного из пакета аргументов, при этом выражения разделяются запятыми.

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

// 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... */ );

Использование этой функции «pass» необходимо, поскольку расширение пакета аргументов происходит путем разделения аргументов вызова функции запятыми, которые не эквивалентны оператору «запятая». Поэтому, 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... содержит хотя бы один аргумент, он будет перенаправлен на вторую версию — пакет параметров может быть пустым, и в этом случае он просто перенаправится на версию завершения, что ничего не сделает.

Шаблоны 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, в конструктор. Оператор распаковки распространит синтаксис пересылки на каждый параметр. Эта конкретная фабричная функция автоматически оборачивает выделенную память в 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...) { /* 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

Шаблоны Variadic часто используются для создания последовательности псевдонимов, называемой 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));
}

См. также

[ редактировать ]

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

  1. ^ Дуглас Грегор и Яакко Ярви (февраль 2008 г.). «Шаблоны с вариациями для C++0x» . Журнал объектных технологий . стр. 31–51.
  2. ^ Дуглас Грегор; Яакко Ярви и Гэри Пауэлл. (февраль 2004 г.). «Шаблоны с вариациями. Номер N1603 = 04-0043 в предварительной рассылке Комитета по стандарту ISO C++».
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 1c2f6b30b4e117efd98434521cff41f4__1687606260
URL1:https://arc.ask3.ru/arc/aa/1c/f4/1c2f6b30b4e117efd98434521cff41f4.html
Заголовок, (Title) документа по адресу, URL1:
Variadic template - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)