c++ - tipo - ¿Cómo puedo iterar sobre una lista de argumentos de plantillas variadas?
recorrer una cola en c (7)
Estoy tratando de encontrar un método para iterar sobre una lista de argumentos de una plantilla variadica. Ahora, al igual que con todas las iteraciones, necesita algún tipo de método para saber cuántos argumentos hay en la lista empaquetada y, lo que es más importante, cómo obtener datos individualmente de una lista de argumentos empaquetados.
La idea general es iterar sobre la lista, almacenar todos los datos de tipo int en un vector, almacenar todos los datos de tipo char * en un vector, y almacenar todos los datos de tipo float, en un vector. Durante este proceso, también debe haber un vector separado que almacene los caracteres individuales en qué orden entraron los argumentos. Por ejemplo, cuando push_back (a_float), también está haciendo un push_back (''f'') que simplemente está almacenando un char individual para conocer el orden de los datos. También podría usar std :: string aquí y simplemente usar + =. El vector fue usado solo como un ejemplo.
Ahora, la forma en que se diseña la cosa es que la función en sí misma se construye usando una macro, a pesar de las malas intenciones, es necesaria, ya que es un experimento. Por lo tanto, es literalmente imposible utilizar una llamada recursiva, ya que la implementación real que albergará todo esto se ampliará en tiempo de compilación; y no puedes recuperar una macro.
A pesar de todos los intentos posibles, todavía estoy atascado en descubrir cómo hacer esto. Así que, en cambio, estoy usando un método más intrincado que implica construir un tipo, y pasar ese tipo a la plantilla varadica, expandirlo dentro de un vector y luego simplemente iterar eso. Sin embargo, no quiero tener que llamar a la función como:
foo(arg(1), arg(2.0f), arg("three");
Entonces la verdadera pregunta es ¿cómo puedo prescindir de tal? Para darles una mejor comprensión de lo que el código realmente está haciendo, he pegado el enfoque optimista que estoy usando actualmente.
struct any {
void do_i(int e) { INT = e; }
void do_f(float e) { FLOAT = e; }
void do_s(char* e) { STRING = e; }
int INT;
float FLOAT;
char *STRING;
};
template<typename T> struct get { T operator()(const any& t) { return T(); } };
template<> struct get<int> { int operator()(const any& t) { return t.INT; } };
template<> struct get<float> { float operator()(const any& t) { return t.FLOAT; } };
template<> struct get<char*> { char* operator()(const any& t) { return t.STRING; } };
#define def(name) /
template<typename... T> /
auto name (T... argv) -> any { /
std::initializer_list<any> argin = { argv... }; /
std::vector<any> args = argin;
#define get(name,T) get<T>()(args[name])
#define end }
any arg(int a) { any arg; arg.INT = a; return arg; }
any arg(float f) { any arg; arg.FLOAT = f; return arg; }
any arg(char* s) { any arg; arg.STRING = s; return arg; }
Sé que esto es desagradable, pero es un experimento puro y no se usará en el código de producción. Es puramente una idea. Probablemente podría hacerse de una mejor manera. Pero un ejemplo de cómo usarías este sistema:
def(foo)
int data = get(0, int);
std::cout << data << std::endl;
end
se parece mucho a Python. también funciona, pero el único problema es cómo llamar a esta función. Aquí hay un ejemplo rápido:
foo(arg(1000));
Estoy obligado a construir un nuevo tipo, que es altamente estético, pero eso no quiere decir que esas macros tampoco lo son. Aparte del punto, solo quiero la opción de hacer: foo (1000);
Sé que se puede hacer, solo necesito algún tipo de método de iteración, o más importante, algún método std :: get para listas de argumentos de plantillas variadas empaquetadas. Lo cual estoy seguro de que se puede hacer.
También para tener en cuenta, soy muy consciente de que esto no es exactamente amigable con los tipos, ya que solo estoy soportando int, float, char * y eso está bien para mí. No estoy requiriendo nada más, y agregaré cheques para usar type_traits para validar que los argumentos pasados sean de hecho los correctos para generar un error de tiempo de compilación si los datos son incorrectos. Esto no es un problema. Tampoco necesito soporte para nada más que estos tipos de POD.
Sería altamente apreciado si pudiera obtener alguna ayuda constructiva, opuesta a los argumentos sobre mi uso puramente ilógico y estúpido de las macros y solo los tipos de POD. Soy consciente de cuán frágil y roto es el código. Este es un experimento complejo, y luego puedo rectificar problemas con datos que no son POD, y hacerlo más seguro y usable.
Gracias por su comprensión, y estoy deseando ayudar.
El rango basado en bucles es maravilloso:
template <typename ...ArgsType >
void function(ArgsType... Args){
helperFunction(Args...);
}
Para mí, this produce la salida:
void helperFunction() {}
template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {
//do what you want with t
function(Args...);
}
Here un ejemplo sin std::any
, que podría ser más fácil de entender para aquellos que no estén familiarizados con std::type_info
:
void helperFunction();
template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args);
template <typename ...ArgsType >
void function(ArgsType... Args){
helperFunction(Args...);
}
void helperFunction() {}
template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {
//do what you want with t
function(Args...);
}
Como era de esperar, esto produce:
#include <iostream>
#include <any>
template <typename... Things>
void printVariadic(Things... things) {
for(const auto p : {things...}) {
std::cout << p.type().name() << std::endl;
}
}
int main() {
printVariadic(std::any(42), std::any(''?''), std::any("C++"));
}
Esta no es la forma típica en que uno usaría plantillas Variadic, en absoluto.
Las iteraciones sobre un paquete variado no son posibles, según las reglas de idioma, por lo que debe recurrir a la recursión.
class Stock
{
public:
bool isInt(size_t i) { return _indexes.at(i).first == Int; }
int getInt(size_t i) { assert(isInt(i)); return _ints.at(_indexes.at(i).second); }
// push (a)
template <typename... Args>
void push(int i, Args... args) {
_indexes.push_back(std::make_pair(Int, _ints.size()));
_ints.push_back(i);
this->push(args...);
}
// push (b)
template <typename... Args>
void push(float f, Args... args) {
_indexes.push_back(std::make_pair(Float, _floats.size()));
_floats.push_back(f);
this->push(args...);
}
private:
// push (c)
void push() {}
enum Type { Int, Float; };
typedef size_t Index;
std::vector<std::pair<Type,Index>> _indexes;
std::vector<int> _ints;
std::vector<float> _floats;
};
Ejemplo (en acción), supongamos que tenemos Stock stock;
:
-
stock.push(1, 3.2f, 4, 5, 4.2f);
se resuelve a (a) como el primer argumento es unaint
-
this->push(args...)
se expande athis->push(3.2f, 4, 5, 4.2f);
, que se resuelve en (b) ya que el primer argumento es unfloat
-
this->push(args...)
se expande athis->push(4, 5, 4.2f);
, que se resuelve en (a) ya que el primer argumento es unaint
-
this->push(args...)
se expande athis->push(5, 4.2f);
, que se resuelve en (a) ya que el primer argumento es unaint
-
this->push(args...)
se expande athis->push(4.2f);
, que se resuelve en (b) ya que el primer argumento es unfloat
-
this->push(args...)
se expande athis->push();
, que se resuelve en (c) ya que no hay argumento, y así termina la recursión
Así:
- Agregar otro tipo para manejar es tan simple como agregar otra sobrecarga, cambiar el primer tipo (por ejemplo,
std::string const&
) - Si se pasa un tipo completamente diferente (digamos
Foo
), no se puede seleccionar ninguna sobrecarga, lo que da como resultado un error en tiempo de compilación.
Una advertencia: la conversión automática significa que un double
seleccionaría la sobrecarga (b) y un short
seleccionaría la sobrecarga (a). Si no se desea, se debe introducir SFINAE, lo que hace que el método sea un poco más complicado (bueno, sus firmas al menos), por ejemplo:
template <typename T, typename... Args>
typename std::enable_if<is_int<T>::value>::type push(T i, Args... args);
Donde is_int
sería algo así como:
template <typename T> struct is_int { static bool constexpr value = false; };
template <> struct is_int<int> { static bool constexpr value = true; };
Otra alternativa, sin embargo, sería considerar un tipo de variante. Por ejemplo:
typedef boost::variant<int, float, std::string> Variant;
Ya existe, con todas las utilidades, puede almacenarse en un vector
, copiarse, etc. ... y se parece mucho a lo que necesita, aunque no utiliza plantillas variables.
No hay una función específica para este momento, pero hay algunas soluciones que puede usar.
Usando la lista de inicialización
Una solución utiliza el hecho de que las subexpresiones de las listas de inicialización se evalúan en orden. int a[] = {get1(), get2()}
ejecutará get1
antes de ejecutar get2
. Quizás las expresiones de dobleces sean útiles para técnicas similares en el futuro. Para llamar a do()
en cada argumento, puedes hacer algo como esto:
template <class... Args>
void doSomething(Args... args) {
int x[] = {args.do()...};
}
Sin embargo, esto solo funcionará cuando do()
devuelve un int
. Puede usar el operador de coma para admitir operaciones que no devuelven un valor adecuado.
template <class... Args>
void doSomething(Args... args) {
int x[] = {(args.do(), 0)...};
}
Para hacer cosas más complejas, puedes ponerlas en otra función:
template <class Arg>
void process(Arg arg, int &someOtherData) {
// You can do something with arg here.
}
template <class... Args>
void doSomething(Args... args) {
int someOtherData;
int x[] = {(process(args, someOtherData), 0)...};
}
Tenga en cuenta que con lambdas genéricos (C ++ 14), puede definir una función para hacer este modelo para usted.
template <class F, class... Args>
void do_for(F f, Args... args) {
int x[] = {(f(args), 0)...};
}
template <class... Args>
void doSomething(Args... args) {
do_for([&](auto arg) {
// You can do something with arg here.
}, args...);
}
Usando recursión
Otra posibilidad es usar recursión. Aquí hay un pequeño ejemplo que define una función similar do_for
como la anterior.
template <class F, class First, class... Rest>
void do_for(F f, First first, Rest... rest) {
f(first);
do_for(f, rest...);
}
template <class F>
void do_for(F f) {
// Parameter pack is empty.
}
template <class... Args>
void doSomething(Args... args) {
do_for([&](auto arg) {
// You can do something with arg here.
}, args...);
}
No puede iterar, pero puede recurse sobre la lista. Verifique el ejemplo de printf () en wikipedia: http://en.wikipedia.org/wiki/C++0x#Variadic_templates
Puede crear un contenedor inicializándolo con su paquete de parámetros entre {}. Siempre que el tipo de parámetros ... sea homogéneo o al menos convertible al tipo de elemento de su contenedor, funcionará. (probado con g ++ 4.6.1)
#include <array>
template <class... Params>
void f(Params... params) {
std::array<int, sizeof...(params)> list = {params...};
}
Puedes usar varias plantillas variadas, esto es un poco desordenado, pero funciona y es fácil de entender. Simplemente tiene una función con la plantilla variadic como tal:
i
c
PKc
Y un helper funciona así:
#include <iostream>
template <typename... Things>
void printVariadic(Things... things) {
for(const auto p : {things...}) {
std::cout << p << std::endl;
}
}
int main() {
printVariadic(1, 2, 3);
}
Ahora cuando llame a "función" se llamará a "helperFunction" y aislará el primer parámetro pasado del resto, esta variable se puede usar para llamar a otra función (o algo así). Luego se llamará "función" una y otra vez hasta que no queden más variables. Tenga en cuenta que es posible que deba declarar helperClass antes de "function".
El código final se verá así:
1
2
3
El código no está probado.
Si desea envolver argumentos a any
, puede usar la siguiente configuración. También hice que any
clase sea un poco más útil, aunque técnicamente no es una clase.
#include <vector>
#include <iostream>
struct any {
enum type {Int, Float, String};
any(int e) { m_data.INT = e; m_type = Int;}
any(float e) { m_data.FLOAT = e; m_type = Float;}
any(char* e) { m_data.STRING = e; m_type = String;}
type get_type() const { return m_type; }
int get_int() const { return m_data.INT; }
float get_float() const { return m_data.FLOAT; }
char* get_string() const { return m_data.STRING; }
private:
type m_type;
union {
int INT;
float FLOAT;
char *STRING;
} m_data;
};
template <class ...Args>
void foo_imp(const Args&... args)
{
std::vector<any> vec = {args...};
for (unsigned i = 0; i < vec.size(); ++i) {
switch (vec[i].get_type()) {
case any::Int: std::cout << vec[i].get_int() << ''/n''; break;
case any::Float: std::cout << vec[i].get_float() << ''/n''; break;
case any::String: std::cout << vec[i].get_string() << ''/n''; break;
}
}
}
template <class ...Args>
void foo(Args... args)
{
foo_imp(any(args)...); //pass each arg to any constructor, and call foo_imp with resulting any objects
}
int main()
{
char s[] = "Hello";
foo(1, 3.4f, s);
}
Sin embargo, es posible escribir funciones para acceder al enésimo argumento en una función de plantilla variadic y aplicar una función a cada argumento, que podría ser una mejor manera de hacer lo que quiera lograr.