c++ - ¿Existe algún caso en el que las funciones vararg sean preferibles a las plantillas variadas?
c++11 variadic-templates (3)
Si proporciona una API de C con implementación de C ++, las plantillas no son una opción para la API. Los varargs son.
Si necesita admitir un compilador que no sea compatible con C ++ 11 o un estándar más reciente, las plantillas variadas no están disponibles. Los varargs son.
Si necesita un cortafuegos de compilación. Es decir, debe ocultar la implementación de la función del encabezado, entonces la plantilla variadic no es una opción. Los varargs son.
En los sistemas con memoria limitada (incrustados), las diferentes funciones generadas por la plantilla pueden generar demasiada hinchazón. Dicho esto, estos sistemas también suelen ser en tiempo real, en cuyo caso los varargs también podrían ser inaceptables debido a la ramificación y al uso de la pila.
Las plantillas Variadic tienen muchas ventajas, pero ¿hay ocasiones en que se deban usar funciones variadic de estilo C (usando <cstdarg>
)?
Quiero agregar a la excelente respuesta de @ user2079303
los varargs también se utilizan en algunos metaprogramaciones (rasgos implementados con SFINAE por ejemplo) debido a su propiedad de ser considerados los últimos en la resolución de sobrecarga.
Digamos que queremos implementar un rasgo para detectar si una clase es construible desde algunos tipos (algo como std::is_constructible :
Una implementación moderna simplificada sería así (no es la única manera: como se señaló, void_t
también se puede usar para implementar el rasgo y pronto (2020?) No necesitaremos ninguno de estos trucos ya que los concepts están en camino con La cláusula de exigencia como ciudadano de primera clase):
template <class T, class... Args> struct Is_constructible {
template <class... Params>
static auto test(Params... params) -> decltype(T{params...}, std::true_type{});
static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test(std::declval<Args>()...))::value;
};
Esto funciona debido a SFINAE : al instanciar la test
, si la semántica no es válida debido a algún nombre dependiente , entonces no es un error difícil, en lugar de eso, la sobrecarga simplemente se ignora.
Si desea saber más acerca de los trucos de rasgos, cómo se implementan y cómo funcionan, puede leer más: sfinae idiom , miembro detector idiom , enable_if idiom .
Entonces, con un tipo X
que puede construirse a partir de 2 pulgadas solamente:
struct X { X(int, int) {}; };
obtenemos estos resultados:
Is_constructible<X, int, int>::value // true
Is_constructible<X, int>::value; // false
La pregunta ahora es si podemos reemplazar la prueba de varargs con plantillas variadic:
template <class T, class... Args> struct Is_constructible_broken {
template <class... Params>
static auto test(Params... params) -> decltype(T{params...}, std::true_type{});
template <class... Params>
static auto test(Params...) -> std::false_type;
static constexpr bool value = decltype(test(std::declval<Args>()...))::value;
};
Y la respuesta es no (al menos no un reemplazo directo). Cuando instanciamos
Is_constructible_broken<X, int, int>::value
obtenemos un error:
error: la llamada de sobrecarga ''
test(int, int)
'' es ambigua
porque ambas sobrecargas son viables y ambas tienen el mismo "rango" en la resolución de sobrecarga. La primera implementación con varargs funciona porque incluso si ambas sobrecargas son viables, la plantilla variada se prefiere sobre la vararg.
Resulta que realmente puedes hacer que funcione con varias plantillas. El truco es agregar un parámetro artificial para test
que es una combinación perfecta para la primera sobrecarga y una conversión para la segunda:
struct overload_low_priority{};
struct overload_high_priority : overload_low_priority {};
template <class T, class... Args> struct Is_constructible2 {
template <class... Params>
static auto test(overload_high_priority, Params... params)
-> decltype(T{params...}, std::true_type{});
template <class... Params>
static auto test(overload_low_priority, Params...) -> std::false_type;
static constexpr bool value
= decltype(test(overload_high_priority{}, std::declval<Args>()...))::value;
};
Pero creo que el varargs es más claro en este caso.
vararg permite utilizar el __attribute__ format
. P.ej
void debug(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void f(float value)
{
debug("value = %d/n", value); // <- will show warning.
}
Desafortunadamente, esto no se puede lograr utilizando plantillas variadic.
Editado: Como señaló Vladimir, olvidé mencionar que el __attribute__ format
no forma parte del estándar, sin embargo, es compatible con GCC y Clang (pero no con Visual Studio). Para obtener más detalles, consulte: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes