¿Cómo puedo escribir una función que acepte un número variable de argumentos? ¿Es esto posible, cómo?
¿Cómo puedo escribir una función que acepte un número variable de argumentos? ¿Es esto posible, cómo?
Respuestas:
Probablemente no deberías, y probablemente puedas hacer lo que quieras hacer de una manera más segura y sencilla. Técnicamente, para usar un número variable de argumentos en C, incluye stdarg.h. A partir de eso, obtendrá el va_list
tipo y las tres funciones que operan en él llamadas va_start()
, va_arg()
y 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;
}
Si me preguntas, esto es un desastre. Se ve mal, es inseguro y está lleno de detalles técnicos que no tienen nada que ver con lo que estás tratando de lograr conceptualmente. En su lugar, considere usar sobrecarga o herencia / polimorfismo, patrón de construcción (como en las operator<<()
secuencias) o argumentos predeterminados, etc. Todo esto es más seguro: el compilador conoce más sobre lo que está tratando de hacer, por lo que hay más ocasiones en que puede detenerse antes de que te vueles la pierna.
...
sintaxis?
printf()
, por ejemplo, la función analiza el argumento de cadena para tokens especiales para determinar cuántos argumentos adicionales debe esperar en la lista de argumentos variables.
<cstdarg>
en C ++ en lugar de<stdarg.h>
En C ++ 11 tiene dos nuevas opciones, como dice la página de referencia de funciones Variadic en la sección Alternativas :
- Las plantillas variables también se pueden usar para crear funciones que toman un número variable de argumentos. A menudo son la mejor opción porque no imponen restricciones sobre los tipos de argumentos, no realizan promociones integrales y de coma flotante, y son de tipo seguro. (desde C ++ 11)
- Si todos los argumentos variables comparten un tipo común, std :: initializer_list proporciona un mecanismo conveniente (aunque con una sintaxis diferente) para acceder a los argumentos variables.
A continuación se muestra un ejemplo que muestra ambas alternativas ( verlo en 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 } ) ;
}
Si está utilizando gcc
o clang
podemos utilizar la variable mágica PRETTY_FUNCTION para mostrar la firma de tipo de la función que puede ser útil para comprender lo que está sucediendo. Por ejemplo usando:
std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
daría como resultado int following para funciones variadas en el ejemplo ( verlo en 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
En Visual Studio puedes usar FUNCSIG .
Actualizar Pre C ++ 11
Pre C ++ 11 la alternativa para std :: initializer_list sería std :: vector o uno de los otros contenedores estándar :
#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 ) ;
}
y la alternativa para plantillas variadas serían funciones variadas, aunque no son de tipo seguro y en general propensas a errores y pueden ser inseguras de usar, pero la única otra alternativa potencial sería usar argumentos predeterminados , aunque eso tiene un uso limitado. El siguiente ejemplo es una versión modificada del código de muestra en la referencia 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 ;
}
El uso de funciones variadas también viene con restricciones en los argumentos que puede pasar, que se detalla en el borrador del estándar C ++ en la sección 5.2.2
Llamada de función, párrafo 7 :
Cuando no hay ningún parámetro para un argumento dado, el argumento se pasa de tal manera que la función receptora puede obtener el valor del argumento invocando va_arg (18.7). Las conversiones estándar lvalue-to-rvalue (4.1), array-to-pointer (4.2) y function-to-pointer (4.3) se realizan en la expresión de argumento. Después de estas conversiones, si el argumento no tiene aritmética, enumeración, puntero, puntero a miembro o tipo de clase, el programa está mal formado. Si el argumento tiene un tipo de clase que no es POD (cláusula 9), el comportamiento es indefinido. [...]
typename
vs es class
superior al intencional? Si es así, por favor explique.
initializer_list
recursivo?
Desde la introducción de plantillas variadas en C ++ 11 y expresiones de plegado en C ++ 17, es posible definir una función de plantilla que, en el sitio de la persona que llama, es invocable como si fuera una función varídica pero con las ventajas de :
Aquí hay un ejemplo para tipos de argumentos mixtos
template<class... Args>
void print(Args... args)
{
(std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");
Y otro con concordancia de tipo forzada para todos los 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!");
^
Más información:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
y Tail...
son lo mismo ", donde " son lo mismo " significa std::conjunction<std::is_same<Head, Tail>...>
. Lea esta última definición como " Head
es lo mismo que todos Tail...
".
en c ++ 11 puedes hacer:
void foo(const std::list<std::string> & myArguments) {
//do whatever you want, with all the convenience of lists
}
foo({"arg1","arg2"});
lista inicializador FTW!
En C ++ 11 hay una manera de hacer plantillas de argumentos variables que conducen a una forma realmente elegante y segura de tener funciones de argumentos variables. El propio Bjarne da un buen ejemplo de printf usando plantillas de argumentos variables en C ++ 11FAQ .
Personalmente, considero esto tan elegante que ni siquiera me molestaría con una función de argumento variable en C ++ hasta que ese compilador sea compatible con las plantillas de argumento variable de C ++ 11.
,
operador con expresiones de pliegue). De lo contrario, no lo creo.
Las funciones variadas de estilo C son compatibles con C ++.
Sin embargo, la mayoría de las bibliotecas de C ++ usan un idioma alternativo, por ejemplo, mientras que la 'c' printf
función toma argumentos variables, el c++ cout
objeto usa una <<
sobrecarga que aborda la seguridad de tipos y los ADT (quizás a costa de la simplicidad de implementación).
std::initializer_lists
... Y esto ya está introduciendo una complejidad masiva en una tarea simple.
Además de varargs o sobrecarga, podría considerar agregar sus argumentos en un std :: vector u otros contenedores (std :: map por ejemplo). Algo como esto:
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);
De esta forma, obtendría seguridad de tipo y el significado lógico de estos argumentos variados sería evidente.
Seguramente este enfoque puede tener problemas de rendimiento, pero no debe preocuparse por ellos a menos que esté seguro de que no puede pagar el precio. Es una especie de enfoque "pitón" para c ++ ...
La única forma es mediante el uso de argumentos variables de estilo C, como se describe aquí . Tenga en cuenta que esta no es una práctica recomendada, ya que no es segura para escribir y propensa a errores.
No hay una forma estándar de C ++ para hacer esto sin recurrir a varargs de estilo C ( ...
).
Por supuesto, hay argumentos predeterminados que "parecen" como un número variable de argumentos según el contexto:
void myfunc( int i = 0, int j = 1, int k = 2 );
// other code...
myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );
Las cuatro llamadas a funciones llaman myfunc
con un número variable de argumentos. Si no se proporciona ninguno, se utilizan los argumentos predeterminados. Sin embargo, tenga en cuenta que solo puede omitir argumentos finales. No hay forma, por ejemplo, de omitir i
y dar solo j
.
Es posible que desee una sobrecarga o parámetros predeterminados: defina la misma función con los parámetros predeterminados:
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 );
}
Esto le permitirá llamar al método con una de cuatro llamadas diferentes:
doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );
... o podrías estar buscando las convenciones de llamadas v_args de C.
Si conoce el rango de número de argumentos que se proporcionarán, siempre puede usar alguna sobrecarga de funciones, como
f(int a)
{int res=a; return res;}
f(int a, int b)
{int res=a+b; return res;}
y así...
Usando plantillas variadas, ejemplo para reproducir console.log
como se ve en JavaScript:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
Nombre de archivo, por ejemplo 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 han dicho otros, varargs de estilo C. Pero también puede hacer algo similar con argumentos predeterminados.
Es posible ahora ... usando boost any y templates En este caso, el tipo de argumentos se puede mezclar
#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 las soluciones C y C ++ para obtener la opción semánticamente más simple, eficiente y dinámica. Si te equivocas, prueba con otra cosa.
// 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;
}
El usuario escribe:
auto arr = spawn<float> (3, 0.1,0.2,0.3);
Semánticamente, esto se ve y se siente exactamente como una función de argumento n. Debajo del capó, puede desempacarlo de una manera u otra.
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;
}
}
Versión simple