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

98

В C ++ 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, это невариная шаблонная функция, принимающая только один параметр шаблона и один аргумент. Каковы правила синтаксиса для этого (примерно)? Как это можно обобщить?

Также: в реализации функции многоточие ( ...) находится в конце интересующего выражения. Есть ли причина, по которой в списке аргументов шаблона и списке параметров многоточие находится посередине?

Ральф Тандецки
источник
2
Кратко о второй части: при "объявлении" пакета параметров шаблона или пакета параметров функции, ...предшествует вводимому идентификатору. При использовании одного или обоих типов пакетов ...символ расширения следует после шаблона выражения.
aschepler

Ответы:

99

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

Лучше всего это можно понять на нескольких примерах. Предположим, у вас есть этот шаблон функции:

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происходит публично от всех базовых классов.

Надеюсь, это поможет.

Наваз
источник
1
Относительно совпадающего выражения; Это должно быть максимально возможное выражение, не так ли? Например, x+args...следует расширить до x+arg0,x+arg1,x+arg2, а не x+arg0,arg1,arg2.
bitmask
Таким образом, ...применяется ко всем расширяемым объектам в шаблоне.
Гонки легкости на орбите
@bitmask: Да. x+args...должен расширяться до x+arg0,x+arg1,x+arg2, а не до x+arg0,arg1,arg2 .
Nawaz
1
@ Jarod42: Я обновлю этот ответ, как только выйдет C ++ 17.
Nawaz
3
@synther: там sizeof...(T)не нужен. Вы можете просто написать:int a[] = { ___ };
Nawaz
48

Следующее взято из выступления Андрея Александреску на 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)... {}

Списки временных аргументов

std::map<Ts...> m;

Будет компилироваться, только если есть возможное совпадение аргументов.

Списки захвата

template <class... Ts> void fun(Ts... vs) {
    auto g = [&vs...] { return gun(vs...); }
    g();
}

Списки атрибутов

struct [[ Ts... ]] IAmFromTheFuture {};

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

typ1232
источник
Ницца. Однако аргументы функции не включены в локусы: P
Potatoswatter
@Potatoswatter Спасибо. О расширении списка аргументов функции не упоминалось.
typ1232
@ typ1232 Извините за редактирование, но мне показалось, что ваш исходный пост был слишком близок к плагиату. Кстати, я тоже смотрел это выступление некоторое время назад - круто.
Walter