Каковы правила для токена "..." в контексте вариативных шаблонов?

В С++ 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 {};

Он указан в спецификации, но нет атрибута, который может быть выражен как тип.