Número variável de argumentos em C ++?

293

Como posso escrever uma função que aceita um número variável de argumentos? Isso é possível, como?

nunos
fonte
49
Neste momento com C ++ 11 as respostas para esta pergunta seria diferem grandemente
K-ballo
1
@ K-ballo eu adicionei C ++ 11 exemplos bem desde que uma recente pergunta pediu a mesma coisa recentemente e senti este necessária para justificar fechando- stackoverflow.com/questions/16337459/...
Shafik Yaghmour
1
Também adicionei opções pré C ++ 11 à minha resposta, portanto agora ela deve cobrir a maioria das opções disponíveis.
Shafik Yaghmour
@ K-ballo, não há nenhuma maneira de fazê-lo em C ++, caso você precise de um tipo de argumento forçado ... nenhuma construção como foo (int ... valores): / Se você não se importa com tipos, sim, modelos variados em C ++ 11 funciona muito bem
GrayWolf

Respostas:

152

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_listtipo, 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.

wilhelmtell
fonte
7
Presumivelmente, você não pode passar referências a uma função varargs porque o compilador não saberá quando passar por valor e quando por referência e porque as macros C subjacentes não necessariamente saberão o que fazer com as referências - já existem restrições sobre o que você pode passar para uma função C com argumentos variáveis ​​devido a coisas como regras de promoção.
Jonathan Leffler
3
é necessário fornecer pelo menos um argumento antes da ...sintaxe?
Lazer 23/06
3
O @Lazer não é um requisito de idioma ou biblioteca, mas a biblioteca padrão não oferece meios de determinar o tamanho da lista. Você precisa que o chamador forneça essas informações ou, de alguma forma, descubra você mesmo. No caso de 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.
wilhelmtell
11
você provavelmente deve usar <cstdarg>em C ++ em vez de<stdarg.h>
newacct
11
O número variável de argumentos é ótimo para depuração ou para funções / métodos que preenchem alguma matriz. Também é ótimo para muitas operações matemáticas, como max, min, sum, average ... Não é uma bagunça quando você não mexe com ela.
Tomáš Zato - Restabelece Monica
395

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 gccou clangpodemos 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. [...]

Shafik Yaghmour
fonte
O seu uso typenamevs está classacima do intencional? Se sim, por favor explique.
kevinarpe 30/09/16
1
@ kevinarpe não intencional, mas não muda nada.
Shafik Yaghmour 30/09/16
Seu primeiro link provavelmente deve ser o de en.cppreference.com/w/cpp/language/variadic_arguments .
Alexey Romanov
é possível fazer uma função tendo uma initializer_listrecursiva?
Idclev 463035818
33

Uma solução C ++ 17: segurança de tipo completo + sintaxe de chamada agradável

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 :

  • seja fortemente seguro;
  • trabalhe sem as informações de tempo de execução do número de argumentos ou sem o uso de um argumento "stop".

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:

  1. Modelos Variadic, também conhecidos como pacote de parâmetros Pacote de parâmetros (desde C ++ 11) - cppreference.com .
  2. Expressões de dobra expressão de dobra (desde C ++ 17) - cppreference.com .
  3. Veja uma demonstração completa do programa no coliru.
YSC
fonte
13
um dia doente espero que eu possa lertemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian
1
@Eladian leu como "Essa coisa é ativada apenas se Heade 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...".
YSC
24

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!

Markus Zancolò
fonte
19

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.

Omniforme
fonte
@donlan - Se você estiver usando C ++ 17, poderá usar expressões fold para tornar as coisas muito mais simples em alguns casos (pense de forma criativa aqui, você pode usar o ,operador com expressões fold). Caso contrário, acho que não.
Onipresente
15

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' printffunção recebe argumentos variáveis, o c++ coutobjeto usa <<sobrecarga que aborda segurança de tipo e ADTs (talvez ao custo da simplicidade da implementação).

Vai
fonte
Além disso, isso parece funcionar apenas no caso de uma função como a impressão, na qual você realmente tem uma iteração de uma única função de argumento em cada argumento. Caso contrário, você está apenas inicializando uma lista e passando a lista para o final std::initializer_lists... E isso já está introduzindo uma enorme complexidade em uma tarefa simples.
Christopher
13

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 ++ ...

Francesco
fonte
6
Um limpador seria não aplicar vetores. Em vez disso, use um argumento de modelo especificando a coleção com estilo STL e repita usando os métodos de início e fim do argumento. Dessa forma, você pode usar std :: vector <T>, std :: array <T, N> do c ++ 11, std :: initializer_list <T> ou até fazer sua própria coleção.
Jens Åkerblom
3
@ JensÅkerblom Concordo, mas este é o tipo de escolha que deve ser analisado para o problema em questão, para evitar o excesso de engenharia. Como essa é uma questão de assinatura da API, é importante entender a relação entre flexibilidade máxima e clareza de intenção / usabilidade / manutenção etc.
Francesco
8

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.

Dave Van den Eynde
fonte
Por propenso a erros, presumo que você queira dizer potencialmente muito, muito perigoso? Especialmente ao trabalhar com entradas não confiáveis.
21411 Kevin Loney
Sim, mas devido aos problemas de segurança do tipo. Pense em todos os possíveis problemas que o printf comum possui: cadeias de formato que não correspondem a argumentos passados ​​e assim por diante. printf usa a mesma técnica, BTW.
Dave Van den Eynde 1/11/2009
7

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 myfunccom 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 ie dar apenas j.

Zoli
fonte
4

É 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.

Kieveli
fonte
2

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...

Dunya Degirmenci
fonte
2

Usando modelos variados, exemplo para reproduzir console.logcomo 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;
    }
};
lama12345
fonte
1

Como outros já disseram, varargs no estilo C. Mas você também pode fazer algo semelhante com argumentos padrão.

Thomas Padron-McCarthy
fonte
Você poderia elaborar?
Fund Monica's Lawsuit
@QPaysTaxes: Para um exemplo de como fazê-lo com argumentos padrão, veja a resposta de Zoltan.
Thomas Padron-McCarthy
0

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);
}
Após o embate
fonte
0

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.

Christopher
fonte
-1

Também poderíamos usar um initializer_list se todos os argumentos forem const e do mesmo tipo

pkumar0
fonte
-2
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

Вениамин Раскин
fonte
1
E comportamento indefinido que não funcionará em nada além de x86.
SS Anne