tipados tipado sinonimo qué lenguajes lenguaje fuertemente fuerte estatico débilmente c++ variadic-functions ellipsis

c++ - qué - tipado sinonimo



¿Por qué C++ no soporta elipsis fuertemente tipados? (8)

¿Puede alguien explicarme por qué C ++, al menos que yo sepa, no implementa una función de puntos suspensivos fuertemente tipada, algo como:

void foo(double ...) { // Do Something }

Lo que significa que, en palabras simples: ''El usuario puede pasar un número variable de términos a la función foo, sin embargo, todos los términos deben ser dobles''


Ahi esta

void foo(std::initializer_list<double> values); // foo( {1.5, 3.14, 2.7} );

que está muy cerca de eso.

También puedes usar plantillas variadas pero se vuelve más discursivo. En cuanto a la razón real, diría que el esfuerzo por incorporar esa nueva sintaxis probablemente no valga la pena: ¿cómo acceder a los elementos individuales? ¿Cómo sabes cuándo parar? ¿Qué lo hace mejor que, por ejemplo, std::initializer_list ?

C ++ tiene algo aún más cercano a eso: paquetes de parámetros no tipográficos.

template < non-type ... values>

como en

template <int ... Ints> void foo() { for (int i : {Ints...} ) // do something with i }

pero el tipo de parámetro de plantilla no-tipo (uhm) tiene algunas restricciones: por ejemplo, no puede ser double .


Basado en la respuesta de Mateo :

void foo () {} template <typename... Rest> void foo (double arg, Rest... rest) { /* do something with arg */ foo(rest...); }

Si el código que usa foo compila, usted sabe que todos los argumentos son convertibles al double .


Históricamente, la sintaxis elipsis ... proviene de C.

Esta bestia complicada se usó para alimentar funciones similares a printf y se usará con va_list , va_start , etc.

Como has señalado, no es de tipos seguros; pero luego C está lejos de ser de tipo seguro, lo que con sus conversiones implícitas y para void* para cualquier tipo de puntero, su truncamiento implícito de integrales / valores de punto flotante, etc.

Como C ++ tenía que estar lo más cerca posible de un superconjunto de C, heredó las elipsis de C.

Desde su inicio, las prácticas de C ++ evolucionaron, y ha habido un fuerte impulso hacia una tipificación más fuerte.

En C ++ 11, esto culminó en:

  • Listas de inicialización, una sintaxis corta para un número variable de valores de un tipo dado: foo({1, 2, 3, 4, 5})
  • Las plantillas variad, que son una bestia propia y permiten escribir un printf tipo seguro, por ejemplo.

Las plantillas Variadic realmente reutilizan los puntos suspensivos ... en su sintaxis, para denotar paquetes de tipos o valores y como un operador de desempaquetar:

void print(std::ostream&) {} template <typename T, typename... Args> void print(std::ostream& out, T const& t, Args const&... args) { print(out << t, args...); // recursive, unless there are no args left // (in that case, it calls the first overload // instead of recursing.) }

Tenga en cuenta los 3 usos diferentes de ... :

  • typename... para declarar un tipo variadic
  • Args const&... para declarar un paquete de argumentos
  • args... para descomprimir el paquete en una expresión

La forma de lograr (más o menos) lo que sugieres es usar varias plantillas

template<typename... Arguments> void foo(Arguments... parameters);

Sin embargo, puede pasar cualquier tipo en el paquete de parámetros ahora. Lo que propones nunca se ha implementado, tal vez podría ser una gran adición al lenguaje, o podría ser demasiado difícil de implementar tal como están las cosas. Siempre puedes intentar escribir una propuesta y enviarla a isocpp.org


Por qué específicamente no se propuso tal cosa (o se propuso y rechazó), no lo sé. Tal cosa ciertamente sería útil, pero agregaría más complejidad al lenguaje. Como Quentin demuestra, ya existe una forma en C ++ 11 de lograr tal cosa con plantillas.

Cuando los Conceptos se agreguen al estándar, tendremos otra forma más concisa:

template <Convertible<double>... Args> void foo(Args... doubles);

o

template <typename... Args> requires Convertible<Args, double>()... void foo(Args... doubles);

o, como @dyp señala :

void foo(Convertible<double>... doubles);

Personalmente, entre la solución actual y las que obtendremos con Concepts, creo que es una solución adecuada al problema. Especialmente porque el último es básicamente lo que originalmente pediste de todos modos.


Porque puedes usar

void foo(std::vector<T> values);


Ya es posible con variadas plantillas y SFINAE:

template <bool...> struct bool_pack; template <bool... v> using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>; template <class... Doubles, class = std::enable_if_t< all_true<std::is_convertible<Doubles, double>{}...>{} >> void foo(Doubles... args) {}

Gracias a Columbo por el buen truco de all_true . También podrá utilizar una expresión de plegado en C ++ 17.

Como los estándares posteriores y futuros se centran en la sintaxis del terser (terse for-loops, plantillas de funciones implícitas ...) es muy posible que su sintaxis propuesta termine en el estándar un día;)


template<typename T, typename... Arguments> struct are_same; template <typename T, typename A1, typename... Args> struct are_same<T, A1, Args...>{ static const bool value = std::is_same<T, A1>::value && are_same<T, Args...>::value;}; template <typename T> struct are_same<T>{static const bool value = true;}; template<typename T, typename... Arguments> using requires_same = std::enable_if_t<are_same<T, Arguments...>::value>; template <typename... Arguments, typename = requires_same<double, Arguments...>> void foo(Arguments ... parameters) { }