Выбрать главу

template<typename…First, typename…Second>

void init(std::pair<First,Second>…)

{

}

int main()

{

  init(std::make_pair(1, 2), std::make_pair(3,4), std::make_pair(0.3, 1e5));

}

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

Кортеж – это структура данных, которая используется для хранения объектов различных типов.

В STL кортеж реализуется шаблонным классом std::tuple, параметрами шаблона являются типы, которые будут храниться в кортеже. Этот класс как нельзя лучше подойдет для наших целей, потому что объекты вызова у нас также задаются параметрами шаблона.

Итак, у нас есть два набора: объекты вызова и данные, передаваемые в вызов. Какой набор упаковать в кортеж, а какой в пакет параметров? Рассмотрим различные способы упаковки наборов.

5.3.2. Способ 1: объекты в пакет, данные в кортеж

При использовании данного способа реализация распределения практически совпадает с описанной в Листинг 65 п. 5.2.2 с той разницей, что для передачи данных используется не переменная, а кортеж (Листинг 68).

Листинг 68. Распределение при упаковке объектов в пакет и данных в кортеж

template <typename CallData>

void Call(CallData& data)  // (1)

{

}

template <typename CallData, typename First, typename…Others>

void Call(CallData& data, First& first, Others&…rest)  // (2)

{

  std::apply(first, data);  // (3)

  Call(data, rest…);      // (4)

}

template <typename… CallData, typename… CallObjects>

void Distribute1(std::tuple<CallData…> data, CallObjects… objects)  // (5)

{

  Call(data, objects…);  // (6)

}

Распределяющая функция объявлена в строке 5. Входными параметрами функции являются кортеж данных вызова data и пакет объектов вызова objects, типы их содержимого задаются параметрами шаблона. Внутри этой функции, в строке 6, происходит первый вызов рекурсивной функции, которой передаются соответствующие аргументы – кортеж и пакет.

Рекурсивная функция объявлена в строке 2. Эта функция извлекает очередной объект из пакета и осуществляет его вызов (строка 3). Здесь используется функция стандартной библиотеки std::apply, которая преобразует содержимое кортежа в список аргументов. Далее, в строке 4, пакет с оставшимися параметрами передается в рекурсивный вызов Call, и процесс повторяется до завершения рекурсии.

5.3.3. Способ 2: объекты в кортеж, данные в пакет

При использовании данного способа необходимо пройти по всем элементам кортежа и осуществить вызовы хранимых в нем объектов, передавая на вход пакет данных. Как осуществить обход содержимого кортежа?

Доступ к элементам кортежа осуществляется с помощью вызова

std::get<index>(tuple),

где index – это порядковый номер элемента (начиная с 0), tuple – имя переменной-кортежа. Проблема в том, что индексы должны быть заранее определены как числовые константы, использование переменной для задания индекса не допускается31. Поэтому здесь нельзя использовать ни циклы, ни функции с входным аргументом – индексом.

Можно попробовать объявить шаблон функции, в которой индекс задается параметром шаблона, а внутри функции изменить индекс и осуществить рекурсивный вызов. По идее, в этом случае для каждого индекса должна была бы сгенерироваться отдельная специализированная функция, однако стандарт не допускает специализацию шаблонов функций32. Но специализация шаблонов классов допустима, поэтому выходом будет обернуть функцию в класс – оболочку и уже для класса объявлять специализацию по индексам. Реализация приведена в Листинг 69.

Листинг 69. Распределение при упаковке объектов в кортеж и данных в пакет

template<std::size_t Index, typename CallObjects, typename… CallData>  // (1)

struct TupleIterator

{

  static void IterateTupleItem(CallObjects& callObjects, CallData…callData)  // (2)

  {

    const std::size_t idx = std::tuple_size_v<CallObjects> – Index;  // (3)

    std::get<idx>(callObjects)(callData…);                         // (4)

    TupleIterator<Index – 1, CallObjects, CallData…>::IterateTupleItem(callObjects, callData…);  // (5)

  }

};

template<typename CallObjects, typename… CallData>  // (6)

struct TupleIterator<0, CallObjects, CallData…>     // (7)

{

  static void IterateTupleItem(CallObjects& callObjects, CallData… callData)  // (8)

  {

вернуться

31

Это связано с тем, что функция получения элемента кортежа по индексу объявлена как шаблон с параметром – числовым значением. Переменные не могут выступать параметрами шаблона.

вернуться

32

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