Quais são as regras para o token “…” no contexto de modelos variadic?

98

No C ++ 11, existem modelos variados como este:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Existem algumas curiosidades sobre isso: A expressão std::forward<Args>(args)...usa ambos Argse, argsmas apenas um ...token. Além disso, std::forwardé uma função de modelo não variável que usa apenas um parâmetro de modelo e um argumento. Quais são as regras de sintaxe para isso (aproximadamente)? Como pode ser generalizado?

Além disso: Na implementação da função, as reticências ( ...) estão no final da expressão de interesse. Existe uma razão para que na lista de argumentos do modelo e na lista de parâmetros as reticências estejam no meio?

Ralph Tandetzky
fonte
2
Resumidamente na segunda parte: Ao "declarar" um pacote de parâmetros de modelo ou pacote de parâmetros de função, o ...vem antes do identificador ser introduzido. Ao usar um ou ambos os tipos de pacotes, o ...vem após o padrão de expressão para expandir.
aschepler

Respostas:

99

No contexto do modelo variadic, as reticências ...são usadas para descompactar o pacote de parâmetros do modelo se ele aparecer no lado direito de uma expressão (chame este padrão de expressão por um momento). A regra é que qualquer padrão que esteja no lado esquerdo de ...é repetido - os padrões descompactados ( agora chame-os de expressões ) são separados por vírgula ,.

Pode ser melhor compreendido por alguns exemplos. Suponha que você tenha este modelo de função:

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)
}

Agora, se eu chamar essa função passando Tcomo {int, char, short}, cada uma das chamadas de função será expandida como:

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) );

No código que você postou, std::forwardsegue o quarto padrão ilustrado pela n()chamada de função.

Observe a diferença entre x(args)...e y(args...)acima!


Você também pode usar ...para inicializar uma matriz como:

struct data_info
{
     boost::any  data;
     std::size_t type_size;
};

std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}

que é expandido para este:

std::vector<data_info> v 
{ 
   {arg0, sizeof(int)},
   {arg1, sizeof(char)},
   {arg2, sizeof(short)}
};

Acabei de perceber que um padrão pode até incluir especificador de acesso public, como mostrado no exemplo a seguir:

template<typename ... Mixins>
struct mixture : public Mixins ...  //pattern = public Mixins
{
    //code
};

Neste exemplo, o padrão é expandido como:

struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN  

Ou seja, mixturederiva publicamente de todas as classes base.

Espero que ajude.

Nawaz
fonte
1
Em relação à expressão que corresponde; Deve ser a maior expressão possível , não é? Por exemplo, x+args...deve ser expandido para x+arg0,x+arg1,x+arg2, não x+arg0,arg1,arg2.
bitmask de
Portanto, o ...aplica-se a todas as entidades expansíveis no padrão.
Leveza raças em Orbit
@bitmask: Sim. x+args...deve expandir para x+arg0,x+arg1,x+arg2, não x+arg0,arg1,arg2 .
Nawaz
1
@ Jarod42: Atualizarei esta resposta assim que o C ++ 17 for lançado.
Nawaz
3
@synther: sizeof...(T)não é necessário lá. Você pode simplesmente escrever:int a[] = { ___ };
Nawaz
48

O que se segue foi retirado da palestra "Variadic Templates are Funadic" por Andrei Alexandrescu em GoingNative 2012. Posso recomendá-lo para uma boa introdução sobre modelos variadic.


Existem duas coisas que podem ser feitas com um pacote variadic. É possível aplicar sizeof...(vs)para obter o número de elementos e expandi-lo.

Regras de expansão

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)

A expansão prossegue de dentro para fora. Ao expandir duas listas em etapa de bloqueio, elas devem ter o mesmo tamanho.

Mais exemplos:

gun(A<Ts...>::hun(vs)...);

Expande tudo Tsna lista de argumentos do modelo de Ae, em seguida, a função huné expandida com todos vs.

gun(A<Ts...>::hun(vs...));

Expande tudo Tsna lista de argumentos do modelo de Ae todos vscomo os argumentos da função para hun.

gun(A<Ts>::hun(vs)...);

Expande a função huncom Tse vsem etapas.

Nota:

Tsnão é um tipo e vsnão é um valor! Eles são apelidos para uma lista de tipos / valores. Qualquer uma das listas pode estar potencialmente vazia. Ambos obedecem apenas a ações específicas. Portanto, o seguinte não é possível:

typedef Ts MyList;  // error!
Ts var;             // error!
auto copy = vs;     // error!

Loci de expansão

Argumentos de função

template <typename... Ts>
void fun(Ts... vs)

Listas de inicializadores

any a[] = { vs... };

Especificadores de base

template <typename... Ts>
struct C : Ts... {};
template <typename... Ts>
struct D : Box<Ts>... { /**/ };

Listas de inicializadores de membros

// Inside struct D
template <typename... Us>
D(Us... vs) : Box<Ts>(vs)... {}

Listas de argumentos temporárias

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

Só será compilado se houver uma correspondência possível para os argumentos.

Listas de captura

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

Listas de atributos

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

Está na especificação, mas ainda não há nenhum atributo que possa ser expresso como um tipo.

typ1232
fonte
Agradável. Os argumentos da função são deixados de fora dos locais, no entanto: P
Potatoswatter
Obrigado @Potatoswatter. A expansão na lista de argumentos da função não foi mencionada.
typ1232
@ typ1232 Desculpe pela edição, mas achei que sua postagem original estava perto demais do plágio. Aliás, eu também assisti aquela palestra há algum tempo - incrível.
Walter