c++ - significado - Función de tipo variable
tipos de variables en c++ y sus rangos (9)
+1 para la solución liliscente C ++ 17.
Para una solución C ++ 11, una forma posible es crear un tipo de rasgos para hacer un "y" de valores múltiples (algo similar a std::conjunction
que, desafortunadamente, está disponible solo desde C ++ 17 ... cuando puede usar el plegado y ya no necesita la std::conjunction
(gracias liliscent)).
template <bool ... Bs>
struct multAnd;
template <>
struct multAnd<> : public std::true_type
{ };
template <bool ... Bs>
struct multAnd<true, Bs...> : public multAnd<Bs...>
{ };
template <bool ... Bs>
struct multAnd<false, Bs...> : public std::false_type
{ };
así foo()
se puede escribir como
template <typename ... Args>
typename std::enable_if<
multAnd<std::is_same<char const *, Args>::value ...>::value>::type
foo (Args ... args )
{
for (const char* arg : {args...}) {
std::cout << arg << "/n";
}
}
Usando C ++ 14, multAnd()
puede escribirse como una función constexpr
template <bool ... Bs>
constexpr bool multAnd ()
{
using unused = bool[];
bool ret { true };
(void)unused { true, ret &= Bs ... };
return ret;
}
así que foo()
convierte
template <typename ... Args>
std::enable_if_t<multAnd<std::is_same<char const *, Args>::value ...>()>
foo (Args ... args )
{
for (const char* arg : {args...}) {
std::cout << arg << "/n";
}
}
--- EDITAR ---
Jarod42 (¡gracias!) Sugiere una forma mucho mejor de desarrollar un multAnd
; algo como
template <typename T, T ...>
struct int_sequence
{ };
template <bool ... Bs>
struct all_of : public std::is_same<int_sequence<bool, true, Bs...>,
int_sequence<bool, Bs..., true>>
{ };
A partir de C ++ 14 se puede usar std::integer_sequence
lugar de imitación ( int_sequence
).
Quiero escribir una función que acepte un número variable de literales de cadena. Si estuviera escribiendo en C, tendría que escribir algo como:
void foo(const char *first, ...);
y entonces la llamada se vería como:
foo( "hello", "world", (const char*)NULL );
Parece que debería ser posible hacerlo mejor en C ++. Lo mejor que he encontrado es:
template <typename... Args>
void foo(const char* first, Args... args) {
foo(first);
foo(args);
}
void foo(const char* first) { /* Do actual work */ }
Llamado:
foo("hello", "world");
Pero temo que la naturaleza recursiva, y el hecho de que no hagamos ningún tipo de verificación hasta que lleguemos a un solo argumento, cometa errores confusos si alguien llama a foo("bad", "argument", "next", 42)
. Lo que quiero escribir, es algo como:
void foo(const char* args...) {
for (const char* arg : args) {
// Real work
}
}
¿Alguna sugerencia?
Edit: También existe la opción de void fn(std::initializer_list<const char *> args)
, pero eso hace que la llamada sea foo({"hello", "world"});
que quiero evitar.
Bueno, cuanto más cerca se pueda llegar a una función que acepte cualquier número arbitrario de const char*
pero nada más usa una función de plantilla y reenvío:
void foo_impl(std::initializer_list<const char*> args)
{
...
}
template <class... ARGS>
auto foo(ARGS&&... args)
-> foo_impl({std::forward<ARGS>(args)...})
{
foo_impl({std::forward<ARGS>(args)...});
}
La sutileza está en permitir las conversiones implícitas normales.
Creo que probablemente quieras algo como esto:
template<class... Args,
std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>
void foo(Args... args ){
for (const char* arg : {args...}) {
std::cout << arg << "/n";
}
}
int main() {
foo("hello", "world");
}
Mientras que todas las demás respuestas resuelven el problema, también puede hacer lo siguiente:
namespace detail
{
void foo(std::initializer_list<const char*> strings);
}
template<typename... Types>
void foo(const Types... strings)
{
detail::foo({strings...});
}
Este enfoque parece (al menos para mí) ser más legible que usar SFINAE y funciona con C ++ 11. Además, te permite mover la implementación de foo
a un archivo cpp
, lo que también podría ser útil.
Edición: al menos con GCC 8.1, mi enfoque parece producir un mejor mensaje de error cuando se llama con argumentos no const char*
:
foo("a", "b", 42, "c");
Esta implementación compila con:
test.cpp: In instantiation of ‘void foo_1(const ArgTypes ...) [with ArgTypes = {const char*, int, const char*, const char*}]’:
test.cpp:17:29: required from here
test.cpp:12:16: error: invalid conversion from ‘int’ to ‘const char*’ [-fpermissive]
detail::foo({strings...});
~~~~~~~~~~~^~~~~~~~~~~~~~
Mientras que basado en SFINAE (implementación de liliscent) produce:
test2.cpp: In function ‘int main()’:
test2.cpp:14:29: error: no matching function for call to ‘foo(const char [6], const char [6], int)’
foo("hello", "world", 42);
^
test2.cpp:7:6: note: candidate: ‘template<class ... Args, typename std::enable_if<(is_same_v<const char*, Args> && ...), int>::type <anonymous> > void foo(Args ...)’
void foo(Args... args ){
^~~
test2.cpp:7:6: note: template argument deduction/substitution failed:
test2.cpp:6:73: error: no type named ‘type’ in ‘struct std::enable_if<false, int>’
std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>
Nota: no es posible hacer coincidir solo literales de cadena. Lo más cerca que puede venir es hacer coincidir una matriz de caracteres const char
.
Para realizar la comprobación de tipo, use una plantilla de función que tome matrices const char
.
Para realizar un bucle sobre ellos con rango basado for
, necesitamos convertirlo en una lista de initializer_list<const char*>
. Podemos hacerlo directamente con llaves en el rango for
instrucción, porque los arreglos decaerán a los punteros.
Así es como se ve la plantilla de la función (nota: esto funciona en cero o más literales de cadena. Si desea que uno o más, cambie la firma de la función para que tome al menos un parámetro.):
template<size_t N>
using cstring_literal_type = const char (&)[N];
template<size_t... Ns>
void foo(cstring_literal_type<Ns>... args)
{
for (const char* arg : {args...})
{
// Real work
}
}
Por supuesto que es posible, esto compila y ejecuta lo que quieres (presta atención)
#include<iostream>
template<class... Char>
// hehe, here is the secret
auto foo(const Char*... args ) ->decltype((char const*)(*std::begin({args...})), (char const*)(*std::end({args...})), void(0))
{
for (const char* arg : {args...}) {
std::cout << arg << "/n";
}
}
int main() {
foo("no", "sense","of","humor");
}
Esta es una solución @liliscent pero con más azúcar y, para complacer a @rubenvb, sin enable_if
. Si piensa que el código adicional es un comentario (que no lo es), tenga en cuenta que verá exactamente la sintaxis que está buscando.
Tenga en cuenta que solo puede alimentar una lista homogénea de cosas que se pueden convertir a char const*
, que fue uno de sus objetivos, según parece.
Usando las expresiones de C ++ 17 fold en el operador de coma, simplemente puede hacer lo siguiente:
#include <iostream>
#include <string>
#include <utility>
template<typename OneType>
void foo_(OneType&& one)
{
std::cout << one;
}
template<typename... ArgTypes>
void foo(ArgTypes&&... arguments)
{
(foo_(std::forward<ArgTypes>(arguments)), ...);
}
int main()
{
foo(42, 43., "Hello", std::string("Bla"));
}
Demo en vivo aquí . Note que usé foo_
dentro de la plantilla, porque no podía molestarme en escribir 4 sobrecargas.
Si realmente desea restringir esto a los literales de cadena, cambie la firma de la función como sugiere la respuesta de Nevin:
#include <cstddef>
#include <iostream>
#include <string>
#include <utility>
template<std::size_t N>
using string_literal = const char(&)[N];
template<std::size_t N>
void foo(string_literal<N> literal)
{
std::cout << literal;
}
template<std::size_t... Ns>
void foo(string_literal<Ns>... arguments)
{
(foo(arguments), ...);
}
int main()
{
foo("Hello", "Bla", "haha");
}
Tenga en cuenta que esto es extremadamente cercano a la sintaxis de C ++ 11 para lograr exactamente lo mismo. Ver, por ejemplo, esta pregunta mía .
Y ahora para algo completamente diferente...
Puede escribir una estructura de tipo de envoltura de la siguiente manera
template <typename, typename T>
struct wrp
{ using type = T; };
template <typename U, typename T>
using wrp_t = typename wrp<U, T>::type;
y una función foo()
que recibe una lista variad de char const *
simplemente se convierte en
template <typename ... Args>
void foo (wrp_t<Args, char const *> ... args)
{
for ( char const * arg : {args...} )
std::cout << "- " << arg << std::endl;
}
El problema es que no puedes llamarlo como quieras.
foo("hello", "world");
porque el compilador no es capaz de deducir los tipos Args...
Obviamente puedes explicitar una lista de tipos ficticios
foo<void, void>("hello", "world");
Pero entiendo que es una solución horrible.
De todos modos, si aceptas pasar por una función de plantilla trivial.
template <typename ... Args>
void bar (Args ... args)
{ foo<Args...>(args...); }
Puedes llamar
bar("hello", "world");
El siguiente es un ejemplo completo de trabajo de C ++ 11
#include <iostream>
template <typename, typename T>
struct wrp
{ using type = T; };
template <typename U, typename T>
using wrp_t = typename wrp<U, T>::type;
template <typename ... Args>
void foo (wrp_t<Args, char const *> ... args)
{
for ( char const * arg : {args...} )
std::cout << "- " << arg << std::endl;
}
template <typename ... Args>
void bar (Args ... args)
{ foo<Args...>(args...); }
int main ()
{
bar("hello", "world"); // compile
// bar("hello", "world", 0); // compilation error
}
#include<type_traits>
#include<iostream>
auto function = [](auto... cstrings) {
static_assert((std::is_same_v<decltype(cstrings), const char*> && ...));
for (const char* string: {cstrings...}) {
std::cout << string << std::endl;
}
};
int main(){
const char b[]= "b2";
const char* d = "d4";
function("a1", b, "c3", d);
//function(a, "b", "c",42); // ERROR
}