Share via


Reticências e modelos variádicos

Este artigo mostra como usar reticências (...) com modelos variadic de C++. As reticências tiveram muitos usos em C e C++. Eles incluem listas de argumentos variáveis para funções. A função printf() da Biblioteca em Runtime C é um dos exemplos mais conhecidos.

Um modelo variádico é um modelo de classe ou função que oferece suporte a um número arbitrário de argumentos. Esse mecanismo é especialmente útil para desenvolvedores de bibliotecas C++: você pode aplicá-lo a modelos de classe e modelos de função e, assim, fornecer uma ampla gama de funcionalidade e flexibilidade seguras para tipos e não triviais.

Sintaxe

As reticências são usadas em duas maneiras por modelos variadic. À esquerda do nome de parâmetro, elas significam um pacote de parâmetros, e à direita do nome de parâmetro, expandem os pacotes de parâmetro em nomes separados.

Aqui está um exemplo básico da sintaxe de definição de modelo de classe variádica:

template<typename... Arguments> class classname;

Para parameter packs e expansões, você pode adicionar espaço em branco ao redor das reticências, com base em sua preferência, conforme mostrado neste exemplo:

template<typename ...Arguments> class classname;

Ou este exemplo:

template<typename ... Arguments> class classname;

Este artigo usa a convenção mostrada no primeiro exemplo (as reticências estão anexadas ao typename).

Nos exemplos anteriores, Arguments é um parameter pack. A classe classname pode aceitar um número variável de argumentos, como nestes exemplos:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

Usando uma definição de modelo de classe variádica, você também pode exigir pelo menos um parâmetro:

template <typename First, typename... Rest> class classname;

Aqui está um exemplo básico de sintaxe de modelo de função variádica:

template <typename... Arguments> returntype functionname(Arguments... args);

O Arguments parameter pack é então expandido para uso, conforme mostrado na próxima seção.

Outras formas de sintaxe de modelo de função variádica são possíveis — incluindo, mas não se limitando a, estes exemplos:

template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);

Especificadores como const também são permitidos:

template <typename... Arguments> returntype functionname(const Arguments&... args);

Tal como as definições de classe de modelo variadic, você pode criar funções que exigem ao menos um parâmetro:

template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);

Os modelos Variadic usam o operador sizeof...() (não relacionado ao operador mais antigo sizeof()):

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

Mais sobre o posicionamento das reticências

Anteriormente, este artigo descreveu o posicionamento de reticências que define pacotes de parâmetros e expansões desta forma: "à esquerda do nome do parâmetro, significa um pacote de parâmetros e, à direita do nome do parâmetro, expande os pacotes de parâmetros em nomes separados". Embora tecnicamente verdadeiro, pode ser confuso na tradução para código. Considerar:

  • Em a template-parameter-list (template <parameter-list>), typename... apresenta um pacote de parâmetros do modelo.

  • Em uma parameter-declaration-clause (func(parameter-list)), as reticências “de nível superior” apresentam um pacote de parâmetro da função, e o posicionamento de reticências é importante:

    // v1 is NOT a function parameter pack:
    template <typename... Types> void func1(std::vector<Types...> v1);
    
    // v2 IS a function parameter pack:
    template <typename... Types> void func2(std::vector<Types>... v2);
    
  • Onde as reticências aparecem imediatamente após o nome de parâmetro, você tem uma expansão do pacote de parâmetros.

Exemplo

Uma boa maneira de ilustrar o mecanismo de modelo de função variádica é usá-lo em uma reescrita de algumas das funcionalidades de printf:

#include <iostream>

using namespace std;

void print() {
    cout << endl;
}

template <typename T> void print(const T& t) {
    cout << t << endl;
}

template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
    cout << first << ", ";
    print(rest...); // recursive call using pack expansion syntax
}

int main()
{
    print(); // calls first overload, outputting only a newline
    print(1); // calls second overload

    // these call the third overload, the variadic template,
    // which uses recursion as needed.
    print(10, 20);
    print(100, 200, 300);
    print("first", 2, "third", 3.14159);
}

Saída

1
10, 20
100, 200, 300
first, 2, third, 3.14159

Observação

A maioria das implementações que incorporam modelos de função variádica usam recursão de alguma forma, mas é ligeiramente diferente da recursão tradicional. A recursão tradicional envolve uma função que chama a ela mesma usando a mesma assinatura. (Pode ser sobrecarregado ou modelo, mas a mesma assinatura é escolhida sempre.) A recursão variada envolve chamar um modelo de função variádica usando números diferentes (quase sempre diminuindo) de argumentos e, assim, acabar com uma assinatura diferente a cada vez. Um “caso base” ainda é necessário, mas a natureza de recursão é diferente.