Каковы правила для токена "..." в контексте вариативных шаблонов?
В С++ 11 есть такие вариативные шаблоны, как этот:
template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
В этом есть некоторые интересные факты: выражение std::forward<Args>(args)...
использует как Args
, так и Args
, но только один токен ...
. Кроме того, std::forward
является невариантной функцией шаблона, принимающей только один параметр шаблона и один аргумент. Каковы правила синтаксиса для этого (примерно)? Как это можно обобщить?
Также: в реализации функции эллипсис (...
) находится в конце интересующего выражения. Есть ли причина, по которой в списке аргументов шаблона и списке параметров эллипсис находится посередине?
Ответы
Ответ 1
В контексте вариационного шаблона эллипсис ...
используется для распаковки пакета параметров шаблона, если он отображается в правой части выражения (назовем этот шаблон выражения на мгновение). Правило заключается в том, что любой шаблон слева от ...
повторяется — распакованные шаблоны (вызывать их выражения сейчас) разделяются запятой ,
.
Это может быть лучше понято некоторыми примерами. Предположим, что у вас есть этот шаблон функции:
template<typename ...T>
void f(T ... args)
{
g( args... ); //pattern = args
h( x(args)... ); //pattern = x(args)
m( y(args...) ); //pattern = args (as argument to y())
n( z<T>(args)... ); //pattern = z<T>(args)
}
Теперь, если я вызываю эту функцию, передавая T
как {int, char, short}
, то каждый вызов функции разворачивается как:
g( arg0, arg1, arg2 );
h( x(arg0), x(arg1), x(arg2) );
m( y(arg0, arg1, arg2) );
n( z<int>(arg0), z<char>(arg1), z<short>(arg2) );
В отправленном вами коде std::forward
следует четвертый шаблон, иллюстрируемый вызовом функции n()
.
Обратите внимание на разницу между x(args)...
и y(args...)
выше!
Вы можете использовать ...
для инициализации массива также как:
struct data_info
{
boost::any data;
std::size_t type_size;
};
std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}
который расширяется до этого:
std::vector<data_info> v
{
{arg0, sizeof(int)},
{arg1, sizeof(char)},
{arg2, sizeof(short)}
};
Я просто понял, что шаблон может включать даже спецификатор доступа, такой как public
, как показано в следующем примере:
template<typename ... Mixins>
struct mixture : public Mixins ... //pattern = public Mixins
{
//code
};
В этом примере шаблон расширяется следующим образом:
struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN
То есть, mixture
выводится публично из всех базовых классов.
Надеюсь, что это поможет.
Ответ 2
Из беседы взято следующее "Шаблоны Variadic funadic" от Andrei Alexandrescu на GoingNative 2012. Я могу порекомендовать его для хорошего введения на вариативных шаблонах.
Есть две вещи, которые можно сделать с вариационным пакетом. Можно применить sizeof...(vs)
, чтобы получить количество элементов и развернуть его.
Правила расширения
Use Expansion
Ts... T1, ..., Tn
Ts&&... T1&&, ..., Tn&&
x<Ts,Y>::z... x<T1,Y>::z, ..., x<Tn,Y>::z
x<Ts&,Us>... x<T1&,U1>, ..., x<Tn&,Un>
func(5,vs)... func(5,v1), ..., func(5,vn)
Расширение происходит вовнутрь наружу. При расширении двух списков в режиме блокировки они должны иметь одинаковый размер.
Дополнительные примеры:
gun(A<Ts...>::hun(vs)...);
Развертывает все Ts
в списке аргументов шаблона A
, а затем функция hun
расширяется со всеми vs
.
gun(A<Ts...>::hun(vs...));
Развертывает все Ts
в списке аргументов шаблона A
и всех vs
в качестве аргументов функции для hun
.
gun(A<Ts>::hun(vs)...);
Развертывает функцию hun
с помощью Ts
и vs
в шаге блокировки.
Примечание:
Ts
не является типом, а vs
не является значением! Это псевдонимы для списка типов/значений. Любой список может быть потенциально пустым. Оба подчиняются только конкретным действиям. Таким образом, следующее невозможно:
typedef Ts MyList; // error!
Ts var; // error!
auto copy = vs; // error!
Локаторы расширения
Аргументы функции
template <typename... Ts>
void fun(Ts... vs)
Списки инициализаторов
any a[] = { vs... };
Спецификаторы базы данных
template <typename... Ts>
struct C : Ts... {};
template <typename... Ts>
struct D : Box<Ts>... { /**/ };
Списки инициализаторов членов
// Inside struct D
template <typename... Us>
D(Us... vs) : Box<Ts>(vs)... {}
Списки аргументов Tempate
std::map<Ts...> m;
Будет только компилироваться, если есть возможное соответствие для аргументов.
Списки захвата
template <class... Ts> void fun(Ts... vs) {
auto g = [&vs...] { return gun(vs...); }
g();
}
Списки атрибутов
struct [[ Ts... ]] IAmFromTheFuture {};
Он указан в спецификации, но нет атрибута, который может быть выражен как тип.