Como posso escrever uma função que aceita um número variável de argumentos? Isso é possível, como?
Como posso escrever uma função que aceita um número variável de argumentos? Isso é possível, como?
Respostas:
Você provavelmente não deveria, e provavelmente pode fazer o que deseja fazer de maneira mais segura e simples. Tecnicamente, para usar o número variável de argumentos em C, você inclui stdarg.h. A partir disso, você obterá o va_list
tipo, bem como três funções que operam nele chamadas va_start()
, va_arg()
e va_end()
.
#include<stdarg.h>
int maxof(int n_args, ...)
{
va_list ap;
va_start(ap, n_args);
int max = va_arg(ap, int);
for(int i = 2; i <= n_args; i++) {
int a = va_arg(ap, int);
if(a > max) max = a;
}
va_end(ap);
return max;
}
Se você me perguntar, isso é uma bagunça. Parece ruim, inseguro e cheio de detalhes técnicos que nada têm a ver com o que você está tentando conceitualmente alcançar. Em vez disso, considere o uso de sobrecarga ou herança / polimorfismo, padrão do construtor (como em operator<<()
fluxos) ou argumentos padrão, etc. Todos são mais seguros: o compilador fica sabendo mais sobre o que você está tentando fazer, para que haja mais ocasiões em que ele pode parar antes de explodir sua perna.
...
sintaxe?
printf()
, por exemplo, a função analisa o argumento da string para tokens especiais para descobrir quantos argumentos extras ele deve esperar na lista de argumentos da variável.
<cstdarg>
em C ++ em vez de<stdarg.h>
No C ++ 11, você tem duas novas opções, conforme a página de referência das funções Variadic na seção Alternativas :
- Modelos variáveis também podem ser usados para criar funções que recebem número variável de argumentos. Geralmente, eles são a melhor escolha, porque não impõem restrições aos tipos de argumentos, não realizam promoções integrais e de ponto flutuante e são seguros para o tipo. (desde C ++ 11)
- Se todos os argumentos de variáveis compartilham um tipo comum, um std :: initializer_list fornece um mecanismo conveniente (embora com uma sintaxe diferente) para acessar argumentos de variáveis.
Abaixo está um exemplo mostrando as duas alternativas ( veja ao vivo ):
#include <iostream>
#include <string>
#include <initializer_list>
template <typename T>
void func(T t)
{
std::cout << t << std::endl ;
}
template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
std::cout << t <<std::endl ;
func(args...) ;
}
template <class T>
void func2( std::initializer_list<T> list )
{
for( auto elem : list )
{
std::cout << elem << std::endl ;
}
}
int main()
{
std::string
str1( "Hello" ),
str2( "world" );
func(1,2.5,'a',str1);
func2( {10, 20, 30, 40 }) ;
func2( {str1, str2 } ) ;
}
Se você estiver usando gcc
ou clang
podemos usar a variável mágica PRETTY_FUNCTION para exibir a assinatura de tipo da função que pode ser útil para entender o que está acontecendo. Por exemplo, usando:
std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
seria o resultado int a seguir para funções variadas no exemplo ( veja ao vivo ):
void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello
No Visual Studio, você pode usar o FUNCSIG .
Atualizar pré C ++ 11
Antes do C ++ 11, a alternativa para std :: initializer_list seria std :: vector ou um dos outros contêineres padrão :
#include <iostream>
#include <string>
#include <vector>
template <class T>
void func1( std::vector<T> vec )
{
for( typename std::vector<T>::iterator iter = vec.begin(); iter != vec.end(); ++iter )
{
std::cout << *iter << std::endl ;
}
}
int main()
{
int arr1[] = {10, 20, 30, 40} ;
std::string arr2[] = { "hello", "world" } ;
std::vector<int> v1( arr1, arr1+4 ) ;
std::vector<std::string> v2( arr2, arr2+2 ) ;
func1( v1 ) ;
func1( v2 ) ;
}
e a alternativa para modelos variados seriam funções variadas, embora não sejam de tipo seguro e, em geral, propensas a erros e possam ser inseguras de usar, mas a única outra alternativa em potencial seria usar argumentos padrão , embora isso tenha uso limitado. O exemplo abaixo é uma versão modificada do código de exemplo na referência vinculada:
#include <iostream>
#include <string>
#include <cstdarg>
void simple_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
while (*fmt != '\0') {
if (*fmt == 'd') {
int i = va_arg(args, int);
std::cout << i << '\n';
} else if (*fmt == 's') {
char * s = va_arg(args, char*);
std::cout << s << '\n';
}
++fmt;
}
va_end(args);
}
int main()
{
std::string
str1( "Hello" ),
str2( "world" );
simple_printf("dddd", 10, 20, 30, 40 );
simple_printf("ss", str1.c_str(), str2.c_str() );
return 0 ;
}
O uso de funções variadas também vem com restrições nos argumentos que você pode passar, detalhadas no rascunho do padrão C ++ na seção 5.2.2
Chamada de função, parágrafo 7 :
Quando não há parâmetro para um dado argumento, o argumento é passado de maneira que a função receptora possa obter o valor do argumento invocando va_arg (18.7). As conversões padrão lvalue para rvalue (4.1), matriz para ponteiro (4.2) e função para ponteiro (4.3) são executadas na expressão de argumento. Após essas conversões, se o argumento não tiver aritmética, enumeração, ponteiro, ponteiro para membro ou tipo de classe, o programa ficará mal formado. Se o argumento tiver um tipo de classe não POD (cláusula 9), o comportamento será indefinido. [...]
typename
vs está class
acima do intencional? Se sim, por favor explique.
initializer_list
recursiva?
Desde a introdução de modelos variados no C ++ 11 e expressões de dobras no C ++ 17, é possível definir uma função de modelo que, no site do chamador, é possível chamar como se fosse uma função varídica, mas com as vantagens de :
Aqui está um exemplo para tipos de argumentos mistos
template<class... Args>
void print(Args... args)
{
(std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");
E outro com correspondência de tipo imposta para todos os argumentos:
#include <type_traits> // enable_if, conjuction
template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
std::cout << head;
(std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!"); // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
// print_same_type(3, ": ", "Hello, ", "World!");
^
Mais Informações:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
e Tail...
é a mesma ", onde " é a mesma " significa std::conjunction<std::is_same<Head, Tail>...>
. Leia esta última definição como " Head
é igual a todos Tail...
".
No c ++ 11, você pode fazer:
void foo(const std::list<std::string> & myArguments) {
//do whatever you want, with all the convenience of lists
}
foo({"arg1","arg2"});
inicializador de lista FTW!
No C ++ 11, existe uma maneira de criar modelos de argumentos variáveis que levam a uma maneira realmente elegante e segura de digitar funções de argumentos variáveis. O próprio Bjarne fornece um bom exemplo de printf usando modelos de argumentos variáveis no C ++ 11FAQ .
Pessoalmente, considero isso tão elegante que nem sequer me incomodaria com uma função de argumento variável em C ++ até que o compilador tenha suporte para modelos de argumento de variável C ++ 11.
,
operador com expressões fold). Caso contrário, acho que não.
Funções variadas do estilo C são suportadas em C ++.
No entanto, a maioria das bibliotecas C ++ usa um idioma alternativo, por exemplo, enquanto a 'c' printf
função recebe argumentos variáveis, o c++ cout
objeto usa <<
sobrecarga que aborda segurança de tipo e ADTs (talvez ao custo da simplicidade da implementação).
std::initializer_lists
... E isso já está introduzindo uma enorme complexidade em uma tarefa simples.
Além de varargs ou sobrecarga, você pode considerar agregar seus argumentos em um std :: vector ou em outros contêineres (std :: map por exemplo). Algo assim:
template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);
Dessa maneira, você obteria segurança de tipo e o significado lógico desses argumentos variáveis seria aparente.
Certamente, essa abordagem pode ter problemas de desempenho, mas você não deve se preocupar com eles, a menos que tenha certeza de que não pode pagar o preço. É uma espécie de abordagem "Pythonic" para c ++ ...
A única maneira é através do uso de argumentos de variável de estilo C, conforme descrito aqui . Observe que essa não é uma prática recomendada, pois não é tipicamente segura e propensa a erros.
Não existe uma maneira C ++ padrão de fazer isso sem recorrer a varargs ( ...
) no estilo C.
É claro que existem argumentos padrão que parecem "número variável de argumentos", dependendo do contexto:
void myfunc( int i = 0, int j = 1, int k = 2 );
// other code...
myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );
Todas as quatro chamadas de função são chamadas myfunc
com um número variável de argumentos. Se nenhum for fornecido, os argumentos padrão serão usados. Observe, no entanto, que você só pode omitir argumentos à direita. Não há como, por exemplo, omitir i
e dar apenas j
.
É possível que você queira sobrecarregar ou parâmetros padrão - defina a mesma função com parâmetros padrão:
void doStuff( int a, double termstator = 1.0, bool useFlag = true )
{
// stuff
}
void doStuff( double std_termstator )
{
// assume the user always wants '1' for the a param
return doStuff( 1, std_termstator );
}
Isso permitirá que você chame o método com uma das quatro chamadas diferentes:
doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );
... ou você pode procurar as convenções de chamada v_args de C.
Se você souber o número de argumentos que serão fornecidos, sempre poderá usar alguma sobrecarga de função, como
f(int a)
{int res=a; return res;}
f(int a, int b)
{int res=a+b; return res;}
e assim por diante...
Usando modelos variados, exemplo para reproduzir console.log
como visto em JavaScript:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
Nome do arquivo, por exemplo js_console.h
:
#include <iostream>
#include <utility>
class Console {
protected:
template <typename T>
void log_argument(T t) {
std::cout << t << " ";
}
public:
template <typename... Args>
void log(Args&&... args) {
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void warn(Args&&... args) {
cout << "WARNING: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void error(Args&&... args) {
cout << "ERROR: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
};
Como outros já disseram, varargs no estilo C. Mas você também pode fazer algo semelhante com argumentos padrão.
Agora é possível ... usando boost any e templates Nesse caso, o tipo de argumentos pode ser misturado
#include <boost/any.hpp>
#include <iostream>
#include <vector>
using boost::any_cast;
template <typename T, typename... Types>
void Alert(T var1,Types... var2)
{
std::vector<boost::any> a( {var1,var2...});
for (int i = 0; i < a.size();i++)
{
if (a[i].type() == typeid(int))
{
std::cout << "int " << boost::any_cast<int> (a[i]) << std::endl;
}
if (a[i].type() == typeid(double))
{
std::cout << "double " << boost::any_cast<double> (a[i]) << std::endl;
}
if (a[i].type() == typeid(const char*))
{
std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
}
// etc
}
}
void main()
{
Alert("something",0,0,0.3);
}
Combine as soluções C e C ++ para a opção semanticamente mais simples, com desempenho e mais dinâmica. Se você errar, tente outra coisa.
// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
T * arr = new T[n];
va_list ap;
va_start(ap, n);
for (size_t i = 0; i < n; i++)
T[i] = va_arg(ap,T);
return arr;
}
O usuário escreve:
auto arr = spawn<float> (3, 0.1,0.2,0.3);
Semanticamente, isso parece e se sente exatamente como uma função n-argument. Sob o capô, você pode desempacotá-lo de uma maneira ou de outra.
int fun(int n_args, ...) {
int *p = &n_args;
int s = sizeof(int);
p += s + s - 1;
for(int i = 0; i < n_args; i++) {
printf("A1 %d!\n", *p);
p += 2;
}
}
Versão simples