c++ - funciones - matriz de caracteres en c
Especificando un tipo para todos los argumentos pasados a la función variadic o a la función de plantilla variadica w/out usando array, vector, structs, etc. (9)
Estoy creando una función (posiblemente función de miembro, no es que importe ... ¿quizás sí?) Que necesita aceptar un número desconocido de argumentos, pero quiero que todos sean del mismo tipo. Sé que podría pasar una matriz o un vector, pero quiero poder aceptar la lista de argumentos directamente sin estructura adicional o incluso con corchetes adicionales. No parece que las funciones variadas en sí mismas sean seguras para el tipo, y no estaba seguro de cómo hacerlo con las funciones de plantillas variadas. Aquí es esencialmente lo que estoy buscando (más que probable que no sea el código correcto, y no con el propósito de obtener listas de dragones, lol):
//typedef for dragon_list_t up here somewhere.
enum Maiden {
Eunice
, Beatrice
, Una_Brow
, Helga
, Aida
};
dragon_list_t make_dragon_list(Maiden...) {
//here be dragons
}
O
template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) {
//here be dragons
}
USO
dragon_list_t dragons_to_slay
= make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida)
;
Intenté algunas cosas similares a las anteriores, sin dados. Sugerencias? Descuidos obvios que pude haber hecho? Sé que puede no ser un gran problema hacer esto en su lugar:
dragon_list_t make_dragon_list(std::array<Maiden> maidens) {
//here be dragons.
}
dragon_list_t dragons_to_slay
= make_dragon_list({Maiden.Eunice, Maiden.Helga, Maiden.Aida})
;
pero preferiría ser capaz de hacerlo de la primera manera si es posible.
Aunque la pregunta está etiquetada como C ++ 11, creo que una solución de conceptos C ++ 17 + valdría la pena agregarlo, ya que ahora hay compatibilidad en GCC y otros seguirán pronto.
primero define un concepto simple
class mytype{};
template<typename T>
concept bool MyType = std::is_same<T, mytype>::value;
entonces simplemente use parámetros de plantilla variadic
template<MyType ... Args>
void func(Args &&... args){
// do something here
}
¡Mucho más fácil con la llegada de los conceptos!
Creo que el siguiente código es útil para su caso:
template <class...>
struct IsAllSame {};
template <class T, class B1>
struct IsAllSame<T, B1> {
static constexpr const bool kValue = std::is_same<T, B1>::value;
};
template <class T, class B1, class... Bn>
struct IsAllSame<T, B1, Bn...> {
static constexpr const bool kValue =
IsAllSame<T, B1>::kValue ? IsAllSame<T, Bn...>::kValue : false;
};
IsAllSame<int>::kValue == true
IsAllSame<bool, int>::kValue == false
IsAllSame<bool, int, int>::kValue == false
Dado que ha incluido la etiqueta C ++ 0x, la respuesta obvia sería buscar listas de inicializadores . Una lista de inicializadores le permite especificar una cantidad de argumentos para un controlador que se convertirá automáticamente a una única estructura de datos para que el procesador los procese.
Su uso primario (¿exclusivo?) Es exactamente para el tipo de situación que ha mencionado, pasando una cantidad de argumentos del mismo tipo para usar al crear algún tipo de lista / matriz / otra colección de objetos. Será compatible con (por ejemplo) std::vector
, por lo que podría usar algo como:
std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};
para crear un vector de tres objetos de dragon
. La mayoría (¿todas?) De las otras colecciones incluirán lo mismo, así que si realmente insistes en una lista de dragones, deberías ser capaz de conseguir eso también con bastante facilidad.
En resumen, probablemente solo deberías crear un vector. No es demasiada sobrecarga, especialmente si usa algo como boost::list_of o la lista de inicializadores de C ++ 0x. La sobrecarga sintáctica es mínima y es más flexible (podría pasar la lista con una cantidad de argumentos conocidos solo en tiempo de ejecución).
Si realmente lo deseaba, podría usar parámetros de plantilla variadic para hacer esto:
// Using pass-by-value since I''m assuming it is primitive:
template< typename T, typename... Args>
void make_dragon_list_internal( dragon_list_t* dragon_list, T t, Args... args )
{
// add T to dragon_list.
make_dragon_list_internal( dragon_list, args... );
}
void make_dragon_list_internal( dragon_list_t* dragon_list )
{
// Finalize dragon_list.
}
template<typename... Args>
dragon_list_t make_dragon_list( Args... args )
{
dragon_list_t dragon_list;
make_dragon_list_internal( &dragon_list, args... );
return dragon_list;
}
Es seguro y extensible (puedes hacer que tome cosas que no sean dragones, si te apetece).
Intentaría mantener las cosas simples, y la solución más simple que puedo pensar es simplemente usar un viejo vector simple. Al usar las funciones de C ++ 0x puede obtener una sintaxis que es similar a (aunque no exactamente) lo que desea:
void foo( std::vector<int> const & v ) {
std::copy( v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ") );
}
int main() {
foo({ 1, 2, 3, 4, 5, 6 }); // note the extra {}
}
Puede aceptar los argumentos de la plantilla variadica y dejar que la verificación de tipo compruebe la validez más adelante cuando se conviertan.
Sin embargo, puede verificar la convertibilidad en el nivel de la interfaz de la función, para hacer uso de la resolución de sobrecarga para rechazar por completo argumentos erróneos, utilizando SFINAE
template<typename R, typename...> struct fst { typedef R type; };
template<typename ...Args>
typename fst<void,
typename enable_if<
is_convertible<Args, ToType>::value
>::type...
>::type
f(Args...);
Para su caso de uso, si conoce los pasos para pasar de std::array<>
a su dragon_list_t
entonces ya lo ha resuelto de acuerdo con la primera opción anterior ("convert-later"):
template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
// here be dragons
}
Si combina esto con el enfoque is_convertible
anterior, tiene una plantilla de rechazo temprano que también sobrecarga la resolución de los argumentos y los rechaza si no es aplicable.
Realmente depende de lo que intentas implementar, exactamente.
Por lo general, enum
indica subtipos de tiempo de ejecución de una clase en particular, o una unión discriminada (boost :: variant). Pero en este caso, quiere pasar la enum
directamente. Además, tiene un conjunto limitado de valores posibles, y cada llamada de función forma un subconjunto. Realmente lo que estás representando es un subconjunto, no varios parámetros en absoluto.
La mejor forma de representar un subconjunto de un conjunto finito es un conjunto de bits. Los grandes conjuntos deben usar std::bitset
; los conjuntos pequeños pueden usar unsigned long
.
enum Maiden_set {
Eunice = 1,
, Beatrice = 2
, Una_Brow = 4
, Helga = 8
, Aida = 16
};
dragon_list_t make_dragon_list(Maiden_set) {
//here be dragons
}
make_dragon_list( Eunice + Beatrice + Helga );
o, dado que parece querer manejar la variación en tiempo de compilación,
template< int Maidens > // parameter is not a Maiden_set because enum+enum=int
dragon_list_t make_dragon_list() {
//here be dragons
}
make_dragon_list< Eunice + Beatrice + Helga >(); // + promotes each enum to int
Debería ser posible generar las potencias de 2 automáticamente usando un operator+
sobrecargado en el tipo de enum
. Pero no estoy seguro de estar en el camino correcto.
Recientemente necesité restringir un paquete de parámetros para que sea solo un tipo, o al menos convertible a ese tipo. Terminé encontrando otra manera:
#include <type_traits>
#include <string>
template <template<typename> class Trait, typename Head, typename ...Tail>
struct check_all {
enum { value = Trait<Head>::value && check_all<Trait, Tail...>::value };
};
template <template<typename> class Trait, typename Head>
struct check_all<Trait, Head> {
enum { value = Trait<Head>::value };
};
template <typename ...Args>
struct foo {
// Using C++11 template alias as compile time std::bind
template <typename T>
using Requirement = std::is_convertible<double, T>;
static_assert(check_all<Requirement, Args...>::value, "Must convert to double");
};
int main() {
foo<int, char, float, double>();
foo<int, std::string>(); // Errors, no conversion
}
Lo que me gustó de esta solución es que puedo aplicar check_all
a otros rasgos también.
Si no usa la template
en el parámetro que no está en el paquete, la función variadic resolverá tener todos los argumentos del mismo tipo.
Aquí hay un ejemplo para una función max
extendida que solo acepta int
s (o tipos convertibles a int
).
int maximum(int n) // last argument must be an `int`
{
return n;
}
template<typename... Args>
int maximum(int n, Args... args) // first argument must be an int
{
return std::max(n, maximum(args...));
}
Explicación: cuando descomprime el paquete de argumentos ( args...
) el compilador busca la mejor sobrecarga. Si el paquete tenía solo un parámetro, entonces el único candidato es el maximum(int)
por lo que el único parámetro debe ser y de tipo int
(o convertible a int
). Si hay más de un elemento en el paquete, entonces el único candidato es el maximum(int, typename...)
por lo que el primer argumento debe ser de tipo int
(o convertible a int
). Es simple probar por inducción que todos los tipos en el paquete deben ser de un tipo convertible a int
).