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.
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de