Zmienna liczba argumentów w C++?

Jak napisać funkcję, która akceptuje zmienną liczbę argumentów? Jak to możliwe?

Author: nunos, 2009-11-01

14 answers

Prawdopodobnie nie powinieneś i prawdopodobnie możesz robić to, co chcesz robić w bezpieczniejszy i prostszy sposób. Technicznie do użycia zmiennej liczby argumentów w C należy dołączyć stdarg.h. z tego otrzymamy typ va_list oraz trzy funkcjonujące na nim funkcje o nazwie va_start(), va_arg() i 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;
}
Jeśli O mnie chodzi, to jest bałagan. Wygląda źle, jest niebezpieczny i jest pełen szczegółów technicznych, które nie mają nic wspólnego z tym, co koncepcyjnie próbujesz osiągnąć. Zamiast tego Rozważ używając przeciążenia lub dziedziczenia / polimorfizmu, wzorca konstruktora (jak w operator<<() w strumieniach) lub domyślnych argumentów itp. Wszystkie są bezpieczniejsze: kompilator dowie się więcej o tym, co próbujesz zrobić, więc jest więcej okazji, że może cię powstrzymać, zanim odstrzelisz nogę.
 122
Author: wilhelmtell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 18:47:22

W C++11 masz dwie nowe opcje, ponieważ strona referencyjna Variadic functions w sekcjiAlternatives stwierdza:

  • zmienne szablony mogą być również używane do tworzenia funkcji, które przyjmują zmienną liczbę argumenty. Są często lepszym wyborem, ponieważ nie nakładają ograniczeń na typy argumentów, nie wykonują promocji całkowych i zmiennoprzecinkowych, a są bezpieczne. (od C++11)
  • Jeśli wszystkie argumenty zmiennych mają wspólny typ, std:: initializer_list dostarcza wygodny mechanizm (aczkolwiek z inną składnią) dostępu do argumentów zmiennych.

Poniżej znajduje się przykład pokazujący obie alternatywy (zobacz to na żywo):

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

Jeśli używasz gcc lub clang możemy użyć PRETTY_FUNCTION magiczna zmienna aby wyświetlić podpis typu funkcji, który może być pomocny w zrozumieniu co się dzieje? Na przykład używając:

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

Spowoduje int następujące dla zmiennych funkcji w przykładzie (zobacz to na żywo):

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

W Visual Studio możesz użyć FUNCSIG.

Update Pre C++11

Pre C++11 alternatywą dla std:: initializer_list będzie std:: vector lub jeden z innych standardowych kontenerów :

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

I alternatywa dla zmienne szablony byłyby zmiennymi funkcjami , chociaż nie są one bezpieczne dla typu i ogólnie podatne na błędy i mogą być niebezpieczne w użyciu , ale jedyną potencjalną alternatywą byłoby użycie domyślnych argumentów , chociaż ma to ograniczone zastosowanie. Poniższy przykład jest zmodyfikowaną wersją przykładowego kodu w linked reference:

#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 ;
} 

Używanie funkcji wariacyjnych wiąże się również z ograniczeniami w argumentach, które możesz przekazać jest szczegółowo w szkicu standardu C++ w sekcji 5.2.2 wywołanie funkcji paragraf 7:

Gdy nie ma parametru dla danego argumentu, argument jest przekazywany w taki sposób, że funkcja odbierająca może uzyskać wartość argumentu przez wywołanie va_arg (18.7). Na wyrażeniu argumentu wykonywane są standardowe konwersje lvalue-to-rvalue (4.1), array-to-pointer (4.2) i function-to-pointer (4.3). Po tych konwersjach, jeśli argument nie ma arytmetyki, wyliczenia, wskaźnika, wskaźnika do członka lub typu klasy, program jest źle uformowany. Jeśli argument ma inny typ klasy niż pod (klauzula 9), zachowanie jest niezdefiniowane. [...]

 292
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-17 19:06:13

Funkcje wariadyczne w stylu C są obsługiwane w C++.

Jednak większość bibliotek C++ używa alternatywnego idiomu, np. podczas gdy funkcja 'c' printf pobiera zmienne argumenty, obiekt {[1] } używa przeciążenia <<, które odnosi się do bezpieczeństwa typu i ADTs (być może kosztem prostoty implementacji).

 16
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 18:46:50

W C++11 istnieje sposób na tworzenie szablonów zmiennych argumentów, które prowadzą do naprawdę eleganckiego i bezpiecznego sposobu tworzenia funkcji zmiennych argumentów. Sam Bjarne podaje ładny przykład printf wykorzystujący zmienne szablony argumentów w C++11faq.

Osobiście uważam to za tak eleganckie, że nawet nie zawracałbym sobie głowy funkcją zmiennej argumentu w C++, dopóki ten kompilator nie będzie miał wsparcia dla szablonów zmiennych argumentów C++11.

 15
Author: Omnifarious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-22 20:20:17

Oprócz varargs lub przeciążenia, możesz rozważyć agregację argumentów w std:: vector lub innych kontenerach (na przykład std::map). Coś takiego:

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

W ten sposób zyskasz bezpieczeństwo typu i logiczne znaczenie tych zmiennych argumentów będzie oczywiste.

Z pewnością takie podejście może mieć problemy z wydajnością, ale nie powinieneś się nimi martwić, chyba że jesteś pewien, że nie możesz zapłacić ceny. Jest to swego rodzaju "Pythoniczne" podejście do c++ ...

 14
Author: Francesco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 19:20:39

W c++11 możesz zrobić:

void foo(const std::list<std::string> & myArguments) {
   //do whatever you want, with all the convenience of lists
}

foo({"arg1","arg2"});

List initializer FTW!

 11
Author: Markus Zancolò,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-17 09:21:49

Jedynym sposobem jest użycie argumentów zmiennych w stylu C, jak opisano tutaj . Zauważ, że nie jest to zalecana praktyka, ponieważ nie jest bezpieczna i podatna na błędy.

 9
Author: Dave Van den Eynde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 18:27:32

Nie ma standardowego sposobu, aby to zrobić bez uciekania się do varargs w stylu C (...).

Istnieją oczywiście domyślne argumenty, które "wyglądają" jak zmienna liczba argumentów w zależności od kontekstu:

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

Wszystkie cztery wywołania funkcji wywołują {[2] } z różną liczbą argumentów. Jeśli nie podano żadnych, używane są argumenty domyślne. Zauważ jednak, że możesz pominąć tylko końcowe argumenty. Nie ma sposobu, na przykład, aby pominąć i i dać tylko j.

 9
Author: Zoltan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 18:40:10

Możliwe jest przeciążenie lub domyślne parametry-zdefiniuj tę samą funkcję z domyślnymi parametrami:

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

To pozwoli Ci wywołać metodę jednym z czterech różnych wywołań:

doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );

... możesz też szukać konwencji wywołujących v_args z C.

 4
Author: Kieveli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 19:44:34

Od wprowadzenia szablonów wariacyjnych w C++11 i wyrażeń składowych w C++17, możliwe jest zdefiniowanie funkcji szablonu, która w miejscu callee może być wywołana tak, jakby była funkcją varidic, ale z zaletami:

  • być silnie Bezpieczny typ;
  • działa bez informacji o czasie wykonywania liczby argumentów lub bez użycia argumentu "stop".

Oto przykład mieszanych typów argumentów

template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

I kolejny z wymuszonym Typ dopasowania dla wszystkich argumentów:

#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!");
                                                                                              ^

Więcej informacji:

  1. zmienne szablony, znane również jako parametr pack Parameter pack (od C++11) - cppreference.com.
  2. wyrażenia Składane fold expression (od C++17) - cppreference.com.
  3. Zobacz pełną demonstrację programu na coliru.
 4
Author: YSC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-22 17:37:08

Jak mówili inni, varargs w stylu C. Ale możesz również zrobić coś podobnego z domyślnymi argumentami.

 2
Author: Thomas Padron-McCarthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 18:33:54

Jeśli znasz Zakres liczby argumentów, które zostaną podane, zawsze możesz użyć funkcji przeciążającej, np.

f(int a)
    {int res=a; return res;}
f(int a, int b)
    {int res=a+b; return res;}

I tak dalej...

 2
Author: Dunya Degirmenci,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-01 19:03:39
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;
   }
}

Wersja prosta

 1
Author: Вениамин Раскин,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-26 10:27:40

Możemy również użyć initializer_list, jeśli wszystkie argumenty są const i tego samego typu

 0
Author: pkumar0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-31 20:49:30