tiempo tau rlc proceso ejemplos descarga dependencia constante condensador con circuito carga capacitor calcular c++ templates template-meta-programming

c++ - tau - dependencia de la carga en un capacitor con el tiempo en el proceso de carga y descarga



ID de constante de tiempo de compilaciĆ³n (15)

Dado lo siguiente:

template<typename T> class A { public: static const unsigned int ID = ?; };

Quiero que ID genere un ID de tiempo de compilación único para cada T. He considerado __COUNTER__ y la biblioteca PP de impulso pero no he tenido éxito hasta ahora. ¿Cómo puedo conseguir esto?

Editar: ID debe ser utilizable como el caso en una declaración de cambio

Editar2: todas las respuestas basadas en la dirección de un método estático o miembro son incorrectas. Si bien crean una ID única, no se resuelven en tiempo de compilación y, por lo tanto, no se pueden usar como casos de una instrucción switch.


Aquí hay un código de C ++ que usa __DATE__ y __TIME__ macro para obtener identificadores únicos para los tipos <T>

Formato:

// __DATE__ "??? ?? ????" // __TIME__ "??:??:??"

Esta es una función hash de mala calidad:

#define HASH_A 8416451 #define HASH_B 11368711 #define HASH_SEED 9796691 / + __DATE__[0x0] * 389 / + __DATE__[0x1] * 82421 / + __DATE__[0x2] * 1003141 / + __DATE__[0x4] * 1463339 / + __DATE__[0x5] * 2883371 / + __DATE__[0x7] * 4708387 / + __DATE__[0x8] * 4709213 / + __DATE__[0x9] * 6500209 / + __DATE__[0xA] * 6500231 / + __TIME__[0x0] * 7071997 / + __TIME__[0x1] * 10221293 / + __TIME__[0x3] * 10716197 / + __TIME__[0x4] * 10913537 / + __TIME__[0x6] * 14346811 / + __TIME__[0x7] * 15485863 unsigned HASH_STATE = HASH_SEED; unsigned HASH() { return HASH_STATE = HASH_STATE * HASH_A % HASH_B; }

Usando la función hash:

template <typename T> class A { public: static const unsigned int ID; }; template <> const unsigned int A<float>::ID = HASH(); template <> const unsigned int A<double>::ID = HASH(); template <> const unsigned int A<int>::ID = HASH(); template <> const unsigned int A<short>::ID = HASH(); #include <iostream> int main() { std::cout << A<float>::ID << std::endl; std::cout << A<double>::ID << std::endl; std::cout << A<int>::ID << std::endl; std::cout << A<short>::ID << std::endl; }


Aquí hay una posible solución basada principalmente en plantillas:

#include<cstddef> #include<functional> #include<iostream> template<typename T> struct wrapper { using type = T; constexpr wrapper(std::size_t N): N{N} {} const std::size_t N; }; template<typename... T> struct identifier: wrapper<T>... { template<std::size_t... I> constexpr identifier(std::index_sequence<I...>): wrapper<T>{I}... {} template<typename U> constexpr std::size_t get() const { return wrapper<U>::N; } }; template<typename... T> constexpr identifier<T...> ID = identifier<T...>{std::make_index_sequence<sizeof...(T)>{}}; // --- struct A {}; struct B {}; constexpr auto id = ID<A, B>; int main() { switch(id.get<B>()) { case id.get<A>(): std::cout << "A" << std::endl; break; case id.get<B>(): std::cout << "B" << std::endl; break; } }

Tenga en cuenta que esto requiere C ++ 14.

Todo lo que tiene que hacer para asociar identificadores secuenciales a una lista de tipos es proporcionar esa lista a una variable de plantilla como en el ejemplo anterior:

constexpr auto id = ID<A, B>;

A partir de ese momento, puede obtener el id. Dado para el tipo dado mediante el método get :

id.get<A>()

Y eso es todo. Puede usarlo en una instrucción switch según lo solicitado y como se muestra en el código de ejemplo.

Tenga en cuenta que, siempre que los tipos se anexen a la lista de clases a la que asociar un id. Numérico, los identificadores son los mismos después de cada compilación y durante cada ejecución.
Si desea eliminar un tipo de la lista, puede seguir utilizando los tipos falsos como marcadores de posición, por ejemplo:

template<typename> struct noLonger { }; constexpr auto id = ID<noLonger<A>, B>;

Esto asegurará que A ya no tenga una identificación asociada y que la que se le haya asignado a B no cambie.
Si no va a eliminar definitivamente A , puede usar algo como:

constexpr auto id = ID<noLonger<void>, B>;

O lo que sea.


Aquí hay una solución pragmática, si está de acuerdo con escribir una sola línea adicional DECLARE_ID(type) para cada type que quiera usar:

#include <iostream> template<class> struct my_id_helper; #define DECLARE_ID(C) template<> struct my_id_helper<C> { enum {value = __COUNTER__ }; } // actually declare ids: DECLARE_ID(int); DECLARE_ID(double); // this would result in a compile error: redefinition of struct my_id_helper<int>’ // DECLARE_ID(int); template<class T> class A { public: static const unsigned int ID = my_id_helper<T>::value; }; int main() { switch(A<int>::ID) { case A<int>::ID: std::cout << "it''s an int!/n"; break; case A<double>::ID: std::cout << "it''s a double!/n"; break; // case A<float>::ID: // error: incomplete type ‘my_id_helper<float>’ default: std::cout << "it''s something else/n"; break; } }


Es posible generar un HASH de tiempo de compilación a partir de una cadena utilizando el código de esta respuesta .

Si puede modificar la plantilla para incluir un entero adicional y usar una macro para declarar la variable:

template<typename T, int ID> struct A { static const int id = ID; }; #define DECLARE_A(x) A<x, COMPILE_TIME_CRC32_STR(#x)>

Usando esta macro para la declaración de tipo, el miembro de id contiene un hash del nombre del tipo. Por ejemplo:

int main() { DECLARE_A(int) a; DECLARE_A(double) b; DECLARE_A(float) c; switch(a.id) { case DECLARE_A(int)::id: cout << "int" << endl; break; case DECLARE_A(double)::id: cout << "double" << endl; break; case DECLARE_A(float)::id: cout << "float" << endl; break; }; return 0; }

A medida que el nombre de tipo se convierte en una cadena, cualquier modificación en el texto del nombre de tipo da como resultado una identificación diferente. Por ejemplo:

static_assert(DECLARE_A(size_t)::id != DECLARE_A(std::size_t)::id, "");

Otro inconveniente se debe a la posibilidad de que se produzca una colisión hash.


Esto es suficiente suponiendo un compilador conforme a los estándares (con respecto a la regla de una definición):

template<typename T> class A { public: static char ID_storage; static const void * const ID; }; template<typename T> char A<T>::ID_storage; template<typename T> const void * const A<T>::ID= &A<T>::ID_storage;

Del estándar de C ++ 3.2.5. Una regla de definición [basic.def.odr] (énfasis en negrita mío):

... Si D es una plantilla y se define en más de una unidad de traducción, los últimos cuatro requisitos de la lista anterior se aplicarán a los nombres del ámbito adjunto de la plantilla utilizado en la definición de la plantilla (14.6.3), y también a nombres dependientes en el punto de ejemplificación (14.6.2). Si las definiciones de D satisfacen todos estos requisitos, entonces el programa se comportará como si hubiera una definición única de D. Si las definiciones de D no satisfacen estos requisitos, entonces el comportamiento no está definido.


Esto no se puede hacer. Una dirección a un objeto estático es lo más cercano que puede llegar a una identificación única, sin embargo, para tomar direcciones de tales objetos (incluso integrales constantes estáticas) se deben definir en alguna parte. Según la regla de una definición, deben definirse dentro de un archivo CPP, lo que no se puede hacer ya que son plantillas. Si define las estáticas dentro de un archivo de encabezado, entonces cada unidad de compilación obtendrá su propia versión implementada, por supuesto, en diferentes direcciones.


Esto parece funcionar bien para mí:

template<typename T> class Counted { public: static int id() { static int v; return (int)&v; } }; #include <iostream> int main() { std::cout<<"Counted<int>::id()="<<Counted<int>::id()<<std::endl; std::cout<<"Counted<char>::id()="<<Counted<char>::id()<<std::endl; }


Lo que suelo usar es esto:

template<typename> void type_id(){} using type_id_t = void(*)();

Como cada instanciación de la función tiene su propia dirección, puede usar esa dirección para identificar tipos:

// Work at compile time constexpr type_id_t int_id = type_id<int>; // Work at runtime too std::map<type_id_t, std::any> types; types[type_id<int>] = 4; types[type_id<std::string>] = "values"s // Find values auto it = types.find(type_id<int>); if (it != types.end()) { // Found it! }


Me encontré con este problema exacto recientemente. Mi solución:

counter.hpp

class counter { static int i; static nexti() { return i++; } };

Counter.cpp:

int counter::i = 0;

templateclass.hpp

#include "counter.hpp" template <class T> tclass { static const int id; }; template <class T> int tclass<T>::id = counter::nexti();

Parece que funciona correctamente en MSVC y GCC, con la única excepción de que no puede usarlo en una declaración de cambio.

Por varias razones, en realidad fui más allá y definí una macro de preprocesador que crea una nueva clase a partir de un parámetro de nombre dado con una ID estática (como la anterior) que se deriva de una base común.


Ok ... entonces este es un truco que encontré en this sitio web. Deberia de funcionar. Lo único que debe hacer es agregar otro parámetro de plantilla a su struct que tome un contador "metaobjeto". Tenga en cuenta que A con int , bool y char tienen identificaciones únicas, pero no se garantiza que int sea 1 y bool será 2 , etc., porque el orden en que se inician las plantillas no es necesariamente conocido.

Otra nota:

Esto no funcionará con Microsoft Visual C ++

#include <iostream> #include "meta_counter.hpp" template<typename T, typename counter> struct A { static const size_t ID = counter::next(); }; int main () { typedef atch::meta_counter<void> counter; typedef A<int,counter> AInt; typedef A<char,counter> AChar; typedef A<bool,counter> ABool; switch (ABool::ID) { case AInt::ID: std::cout << "Int/n"; break; case ABool::ID: std::cout << "Bool/n"; break; case AChar::ID: std::cout << "Char/n"; break; } std::cout << AInt::ID << std::endl; std::cout << AChar::ID << std::endl; std::cout << ABool::ID << std::endl; std::cout << AInt::ID << std::endl; while (1) {} }

Aquí está meta_counter.hpp :

// author: Filip Roséen <[email protected]> // source: http://b.atch.se/posts/constexpr-meta-container #ifndef ATCH_META_COUNTER_HPP #define ATCH_META_COUNTER_HPP #include <cstddef> namespace atch { namespace { template<class Tag> struct meta_counter { using size_type = std::size_t; template<size_type N> struct ident { friend constexpr size_type adl_lookup (ident<N>); static constexpr size_type value = N; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - template<class Ident> struct writer { friend constexpr size_type adl_lookup (Ident) { return Ident::value; } static constexpr size_type value = Ident::value; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - template<size_type N, int = adl_lookup (ident<N> {})> static constexpr size_type value_reader (int, ident<N>) { return N; } template<size_type N> static constexpr size_type value_reader (float, ident<N>, size_type R = value_reader (0, ident<N-1> ())) { return R; } static constexpr size_type value_reader (float, ident<0>) { return 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - template<size_type Max = 64> static constexpr size_type value (size_type R = value_reader (0, ident<Max> {})) { return R; } template<size_type N = 1, class H = meta_counter> static constexpr size_type next (size_type R = writer<ident<N + H::value ()>>::value) { return R; } }; }} #endif /* include guard */


Otra alternativa es considerar los siguientes Data clase con el type campo de miembro único y estático:

template <class T> class Data { public: static const std::type_index type; }; // do [static data member initialization](http://.com/q/11300652/3041008) // by [generating unique type id](http://.com/q/26794944/3041008) template <class T> std::type_index const Data<T>::type = std::type_index(typeid(T));

produce la salida ( MinGWx64-gcc4.8.4 -std=c++11 -O2 )

printf("%s %s/n", Data<int>::type.name(), Data<float>::type.name()) //prints "i f"

No es exactamente una Id. De enteros o una cadena bastante imprimible, ni un constexpr , pero se puede usar como un índice en contenedores asociativos (no) ordenados .
También parece funcionar si el encabezado Data.h se incluye en varios archivos (mismos valores hashCode() ).


Si los valores no monótonos y un intptr_t son aceptables:

template<typename T> struct TypeID { private: static char id_ref; public: static const intptr_t ID; }; template<typename T> char TypeID<T>::id_ref; template<typename T> const intptr_t TypeID<T>::ID = (intptr_t)&TypeID<T>::id_ref;

Si debe tener ints, o debe tener valores incrementales monótonamente, creo que usar constructores estáticos es la única manera de hacerlo:

// put this in a namespace extern int counter; template<typename T> class Counter { private: Counter() { ID_val = counter++; } static Counter init; static int ID_val; public: static const int &ID; }; template<typename T> Counter<T> Counter<T>::init; template<typename T> int Counter<T>::ID_val; template<typename T> const int &Counter<T>::ID = Counter<T>::ID_val; // in a non-header file somewhere int counter;

Tenga en cuenta que ninguna de estas técnicas es segura si las comparte entre bibliotecas compartidas y su aplicación.


Tuve un problema similar hace unos meses. Estaba buscando una técnica para definir identificadores que son iguales en cada ejecución.
Si esto es un requisito, here hay otra pregunta que explora más o menos el mismo problema (por supuesto, viene junto con su buena respuesta).
De todos modos, no usé la solución propuesta. Sigue una descripción de lo que hice esa vez.

Puede definir una función constexpr como la siguiente:

static constexpr uint32_t offset = 2166136261u; static constexpr uint32_t prime = 16777619u; constexpr uint32_t fnv(uint32_t partial, const char *str) { return str[0] == 0 ? partial : fnv((partial^str[0])*prime, str+1); } inline uint32_t fnv(const char *str) { return fnv(offset, str); }

Entonces una clase como esta de la cual heredar:

template<typename T> struct B { static const uint32_t id() { static uint32_t val = fnv(T::identifier); return val; } };

La expresión de CRTP hace el resto.
Como ejemplo, puede definir una clase derivada como sigue:

struct C: B<C> { static const char * identifier; }; const char * C::identifier = "ID(C)";

Siempre que proporcione identificadores diferentes para diferentes clases, tendrá valores numéricos únicos que se pueden usar para distinguir entre los tipos.

Los identificadores no están obligados a formar parte de las clases derivadas. Como ejemplo, puede proporcionarlos por medio de un rasgo:

template<typename> struct trait; template<> struct trait { static const char * identifier; }; // so on with all the identifiers template<typename T> struct B { static const uint32_t id() { static uint32_t val = fnv(trait<T>::identifier); return val; } };

Ventajas:

  • Fácil de implementar.
  • Sin dependencias
  • Los valores numéricos son los mismos durante cada ejecución.
  • Las clases pueden compartir el mismo identificador numérico si es necesario.

Desventajas:

  • Propenso a errores: copiar y pegar puede convertirse rápidamente en su peor enemigo.

Sigue un ejemplo mínimo y funcional de lo que se ha descrito anteriormente.
Adapte el código para poder usar el método del miembro de ID en una declaración de switch :

#include<type_traits> #include<cstdint> #include<cstddef> static constexpr uint32_t offset = 2166136261u; static constexpr uint32_t prime = 16777619u; template<std::size_t I, std::size_t N> constexpr std::enable_if_t<(I == N), uint32_t> fnv(uint32_t partial, const char (&)[N]) { return partial; } template<std::size_t I, std::size_t N> constexpr std::enable_if_t<(I < N), uint32_t> fnv(uint32_t partial, const char (&str)[N]) { return fnv<I+1>((partial^str[I])*prime, str); } template<std::size_t N> constexpr inline uint32_t fnv(const char (&str)[N]) { return fnv<0>(offset, str); } template<typename T> struct A { static constexpr uint32_t ID() { return fnv(T::identifier); } }; struct C: A<C> { static constexpr char identifier[] = "foo"; }; struct D: A<D> { static constexpr char identifier[] = "bar"; }; int main() { constexpr auto val = C::ID(); switch(val) { case C::ID(): break; case D::ID(): break; default: break; } }

Tenga en cuenta que si desea usar ID en una expresión no constante, debe definir en alguna parte el identifier como sigue:

constexpr char C::identifier[]; constexpr char D::identifier[];

Una vez que lo hiciste, puedes hacer algo como esto:

int main() { constexpr auto val = C::ID(); // Now, it is well-formed auto ident = C::ID(); // ... }


Usando this contador de expresión constante:

template <class T> class A { public: static constexpr int ID() { return next(); } }; class DUMMY { }; int main() { std::cout << A<char>::ID() << std::endl; std::cout << A<int>::ID() << std::endl; std::cout << A<BETA>::ID() << std::endl; std::cout << A<BETA>::ID() << std::endl; return 0; }

salida: (GCC, C ++ 14)

1 2 3 3

El inconveniente es que tendrá que adivinar un límite superior en el número de clases derivadas para que funcione el contador de expresiones constantes.


Use la dirección de memoria de una función estática.

template<typename T> class A { public: static void ID() {} };

(&(A<int>::ID)) será diferente de (&(A<char>::ID)) y así sucesivamente.