Jak napisać funkcję, która akceptuje zmienną liczbę argumentów? Czy to możliwe, jak?
Jak napisać funkcję, która akceptuje zmienną liczbę argumentów? Czy to możliwe, jak?
Odpowiedzi:
Prawdopodobnie nie powinieneś i prawdopodobnie możesz robić to, co chcesz robić w bezpieczniejszy i prostszy sposób. Technicznie, aby użyć zmiennej liczby argumentów w C, dołączamy stdarg.h. Od tego otrzymasz va_list
typ, a także trzy działają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 mnie zapytasz, to jest bałagan. Wygląda źle, jest niebezpieczny i pełen technicznych szczegółów, które nie mają nic wspólnego z tym, co koncepcyjnie próbujesz osiągnąć. Zamiast tego rozważ użycie przeciążenia lub dziedziczenia / polimorfizmu, wzorca konstruktora (jak w operator<<()
strumieniach) lub domyślnych argumentów itp. Wszystko to jest bezpieczniejsze: kompilator dowie się więcej o tym, co próbujesz zrobić, więc jest więcej okazji, aby zatrzymać zanim zdejmiesz nogę.
...
składnią?
printf()
, na przykład, funkcja analizuje argument ciągu dla specjalnych tokenów, aby dowiedzieć się, ile dodatkowych argumentów powinien się spodziewać na liście zmiennych.
<cstdarg>
w C ++ zamiast<stdarg.h>
W C ++ 11 masz dwie nowe opcje, ponieważ strona referencyjna funkcji Variadic w sekcji Alternatywy mówi:
- Szablony variadic mogą być również używane do tworzenia funkcji, które przyjmują zmienną liczbę argumentów. Często są lepszym wyborem, ponieważ nie nakładają ograniczeń na typy argumentów, nie przeprowadzają promocji integralnych i zmiennoprzecinkowych oraz są bezpieczne dla typu. (od C ++ 11)
- Jeśli wszystkie zmienne argumenty mają wspólny typ, lista std :: initializer_list zapewnia wygodny mechanizm (choć z inną składnią) dostępu do argumentów zmiennych.
Poniżej znajduje się przykład pokazujący obie alternatywy ( zobacz 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ć magicznej zmiennej PRETTY_FUNCTION, aby wyświetlić podpis typu funkcji, który może być pomocny w zrozumieniu, co się dzieje. Na przykład za pomocą:
std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
w poniższym przykładzie wyniki dla funkcji variadic ( zobacz 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 .
Zaktualizuj wersję C ++ 11
Przed wersją C ++ 11 alternatywą dla std :: initializer_list byłby 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 ) ;
}
a alternatywą dla szablonów variadic byłyby funkcje variadic, chociaż nie są one bezpieczne pod względem typu i generalnie 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 to zmodyfikowana wersja przykładowego kodu w powiązanym odnośniku:
#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 ;
}
Korzystanie z funkcji variadic wiąże się również z ograniczeniami w argumentach, które można przekazać, co jest szczegółowo opisane w projekcie standardu C ++ w sekcji 5.2.2
Wywołanie funkcji w paragrafie 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). Standardowe konwersje lvalue-to-rvalue (4.1), array-to-pointer (4.2) i function-to-pointer (4.3) są wykonywane na wyrażeniu argumentu. Po tych konwersjach, jeśli argument nie ma arytmetyki, wyliczenia, wskaźnika, wskaźnika do elementu lub typu klasy, program jest źle sformułowany. Jeśli argument ma typ klasy innej niż POD (klauzula 9), zachowanie jest niezdefiniowane. [...]
typename
vs class
użytkowania powyżej zamierzone? Jeśli tak, proszę wyjaśnić.
initializer_list
rekurencję?
Od czasu wprowadzenia szablonów variadic w C ++ 11 i wyrażeń fold w C ++ 17 możliwe jest zdefiniowanie funkcji szablonu, która w miejscu wywołującym jest wywoływana tak, jakby była funkcją varidic, ale z zaletami :
Oto przykład mieszanych typów argumentów
template<class... Args>
void print(Args... args)
{
(std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");
I kolejne z wymuszonym dopasowaniem typu 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:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
i Tail...
są takie same ”, gdzie „ są takie same ” oznacza std::conjunction<std::is_same<Head, Tail>...>
. Przeczytaj ostatnią definicję, ponieważ „ Head
jest taki sam jak wszystkie Tail...
”.
w c ++ 11 możesz wykonać:
void foo(const std::list<std::string> & myArguments) {
//do whatever you want, with all the convenience of lists
}
foo({"arg1","arg2"});
inicjator listy FTW!
W C ++ 11 istnieje sposób tworzenia szablonów zmiennych argumentów, które prowadzą do naprawdę eleganckiego i bezpiecznego typu funkcji do wykonywania zmiennych argumentów. Sam Bjarne podaje ładny przykład printf wykorzystujący szablony zmiennych argumentów w C ++ 11FAQ .
Osobiście uważam to za tak eleganckie, że nawet nie zawracałbym sobie głowy funkcją zmiennej argumentów w C ++, dopóki ten kompilator nie będzie obsługiwał szablonów zmiennych zmiennych C ++ 11.
,
operatora z wyrażeniami fold). W przeciwnym razie nie sądzę.
Funkcje variadic w stylu C są obsługiwane w C ++.
Jednak większość bibliotek C ++ używa alternatywnego idiomu, np. Podczas gdy 'c' printf
funkcja przyjmuje zmienne argumenty, c++ cout
obiekt używa <<
przeciążenia, które dotyczy bezpieczeństwa typu i narzędzi ADT (być może kosztem prostoty implementacji).
std::initializer_lists
... A to już wprowadza ogromną złożoność prostego zadania.
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 zyskałbyś bezpieczeństwo typu i logiczne znaczenie tych różnych argumentów byłoby oczywiste.
Z pewnością takie podejście może mieć problemy z wydajnością, ale nie powinieneś się tym martwić, chyba że masz pewność, że nie możesz zapłacić ceny. Jest to swego rodzaju „Pythońskie” podejście do c ++ ...
Jedynym sposobem jest użycie argumentów zmiennych w stylu C, jak opisano tutaj . Pamiętaj, że nie jest to zalecana praktyka, ponieważ nie jest bezpieczna dla maszyn i podatna na błędy.
Nie ma standardowego sposobu na zrobienie tego 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ą myfunc
zmienną liczbę argumentów. Jeśli nie podano żadnych, używane są domyślne argumenty. Należy jednak pamiętać, że można pominąć tylko końcowe argumenty. Nie można na przykład pominąć i
i podać tylko j
.
Możliwe, że chcesz przeciążenia lub parametrów domyślnych - zdefiniuj tę samą funkcję z parametrami domyślnymi:
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 );
}
Umożliwi to wywołanie metody za pomocą jednego z czterech różnych wywołań:
doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );
... lub możesz szukać konwencji wywoływania v_args z C.
Jeśli znasz zakres liczby argumentów, które zostaną podane, zawsze możesz użyć przeciążenia funkcji, np
f(int a)
{int res=a; return res;}
f(int a, int b)
{int res=a+b; return res;}
i tak dalej...
Używając szablonów variadic, przykład do reprodukcji, console.log
jak widać w JavaScript:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
Nazwa pliku np . 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;
}
};
Jak powiedzieli inni, varargs w stylu C. Ale możesz również zrobić coś podobnego z domyślnymi argumentami.
Jest to możliwe teraz ... przy użyciu boost any i szablonów W tym przypadku typ argumentów można mieszać
#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);
}
Połącz rozwiązania C i C ++, aby uzyskać najprostszą semantycznie, wydajną i najbardziej dynamiczną opcję. Jeśli spieprzysz, spróbuj czegoś innego.
// 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;
}
Użytkownik pisze:
auto arr = spawn<float> (3, 0.1,0.2,0.3);
Semantycznie wygląda to i działa dokładnie jak funkcja n-argumentowa. Pod maską możesz go rozpakować w taki czy inny sposób.
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 zwykła