¿Por qué no puedo usar el valor de flotación como un parámetro de plantilla?

Cuando bash usar float como parámetro de plantilla, el comstackdor llora por este código, mientras que int funciona bien.

¿Es porque no puedo usar float como un parámetro de plantilla?

 #include using namespace std; template  class GenericClass { private: T value; public: GenericClass() { value = defaultValue; } T returnVal() { return value; } }; int main() { GenericClass  gcInteger; GenericClass  gcFlaot; cout << "\n sum of integer is "<<gcInteger.returnVal(); cout << "\n sum of float is "<<gcFlaot.returnVal(); return 0; } 

Error:

 main.cpp: In function `int main()': main.cpp:25: error: `float' is not a valid type for a template constant parameter main.cpp:25: error: invalid type in declaration before ';' token main.cpp:28: error: request for member `returnVal' in `gcFlaot', which is of non-class type `int' 

Estoy leyendo “Estructuras de datos para progtwigdores de juegos” de Ron Penton, el autor pasa una float , pero cuando lo bash no parece comstackr.

El estándar actual de C ++ no permite el uso de los literales float (es decir, el número real) o de cadena de caracteres como parámetros de plantilla sin tipo . Por supuesto, puede utilizar los tipos float y char * como argumentos normales.

¿Tal vez el autor está utilizando un comstackdor que no sigue el estándar actual?

LA RESPUESTA SIMPLE

El estándar no permite puntos flotantes como argumentos de plantilla sin tipo , que se pueden leer en la siguiente sección del estándar C ++ 11;

14.3.2 / 1 Argumentos sin modelo de plantilla [temp.arg.nontype]

Un argumento de plantilla para un parámetro de plantilla que no sea de tipo y sin plantilla será uno de:

  • para un parámetro de plantilla sin tipo de tipo integral o de enumeración, una expresión de constante convertida (5.19) del tipo del parámetro de plantilla;

  • el nombre de un parámetro de plantilla sin tipo; o

  • una expresión constante (5.19) que designa la dirección de un objeto con una duración de almacenamiento estática y un enlace externo o interno o una función con enlace externo o interno, incluyendo plantillas de función e id. de plantilla de función pero excluyendo miembros de clase no estáticos, expresados ​​(ignorando paréntesis) como & id-expression, excepto que & puede omitirse si el nombre se refiere a una función o matriz y debe omitirse si el parámetro-plantilla correspondiente es una referencia; o

  • una expresión constante que se evalúa como un valor de puntero nulo (4.10); o

  • una expresión constante que evalúa a un valor de puntero de miembro nulo (4.11); o

  • un puntero al miembro expresado como se describe en 5.3.1.


Pero … pero … ¿POR QUÉ?

Probablemente se deba al hecho de que los cálculos de punto flotante no se pueden representar de manera exacta. Si se permitiera, podría / daría lugar a un comportamiento erróneo / extraño al hacer algo así;

 func<1/3.f> (); func<2/6.f> (); 

Queríamos llamar a la misma función dos veces, pero este podría no ser el caso, ya que no se garantiza que la representación en coma flotante de los dos cálculos sea exactamente la misma.


¿Cómo representaría los valores de punto flotante como argumentos de plantilla?

Con C++11 podría escribir algunas expresiones constantes bastante avanzadas ( constexpr ) que calcularían el numerador / denominador de un tiempo de comstackción de valores flotantes y luego pasarían estos dos como argumentos enteros separados.

Recuerde definir algún tipo de umbral para que los valores de coma flotante cercanos produzcan el mismo numerador / denominador ; de lo contrario, es inútil ya que arrojará el mismo resultado mencionado anteriormente como razón para no permitir que los valores de coma flotante no sean de tipo argumentos de la plantilla .

Solo para proporcionar una de las razones por las cuales esto es una limitación (al menos en el estándar actual).

Cuando se combinan las especializaciones de plantilla, el comstackdor coincide con los argumentos de la plantilla, incluidos los argumentos sin tipo.

Por su propia naturaleza, los valores de punto flotante no son exactos y su implementación no está especificada por el estándar de C ++. Como resultado, es difícil decidir cuándo coinciden realmente dos argumentos de tipo no flotante con punto flotante:

 template  void foo () ; void bar () { foo< (1.0/3.0) > (); foo< (7.0/21.0) > (); } 

Estas expresiones no necesariamente producen el mismo “patrón de bits”, por lo que no sería posible garantizar que utilizaran la misma especialización, sin una redacción especial para cubrir esto.

De hecho, no puede usar literales float como parámetros de plantilla. Consulte la sección 14.1 (“Un parámetro de plantilla no de tipo debe tener uno de los siguientes tipos (opcionalmente cv-qualified) …”) del estándar.

Puede usar una referencia al flotador como un parámetro de plantilla:

 template  class GenericClass . . float const c_four_point_six = 4.6; // at global scope . . GenericClass < float, c_four_point_six> gcFlaot; 

Envuelva el / los parámetro (s) en su propia clase como constexprs. Efectivamente, esto es similar a un rasgo, ya que parametriza la clase con un conjunto de flotantes.

 class MyParameters{ public: static constexpr float Kd =1.0f; static constexpr float Ki =1.0f; static constexpr float Kp =1.0f; }; 

y luego crea una plantilla tomando el tipo de clase como parámetro

  template  class PidController { // define short hand constants for the PID tuning parameters static constexpr NUM Kp = TUNING_PARAMS::Kp; static constexpr NUM Ki = TUNING_PARAMS::Ki; static constexpr NUM Kd = TUNING_PARAMS::Kd; .... code to actually do something ... }; 

y luego úsalo así …

 int main (){ PidController controller; ... ... } 

Esto permite al comstackdor garantizar que solo se crea una instancia única del código para cada instancia de plantilla con el mismo paquete de parámetros. Eso soluciona todos los problemas y puede usar flotantes y dobles como constexpr dentro de la clase de plantilla.

Si está bien tener un valor por defecto fijo por tipo, puede crear un tipo para definirlo como una constante y especializarlo según sea necesario.

 template  struct MyTypeDefault { static const T value; }; template  const T MyTypeDefault::value = T(); template <> struct MyTypeDefault { static const double value; }; const double MyTypeDefault::value = 1.0; template  class MyType { public: MyType() { value = MyTypeDefault::value; } private: T value; }; 

Si tiene C ++ 11, puede usar constexpr cuando defina el valor predeterminado. Con C ++ 14, MyTypeDefault puede ser una variable de plantilla que es un poco más sintáctica.

 //C++14 template  constexpr T MyTypeDefault = T(); template <> constexpr double MyTypeDefault = 1.0; template  class MyType { private: T value = MyTypeDefault; }; 

Siempre puedes fingir …

 #include  template  struct Float { static constexpr float value() { return (float)NUM / (float)DEN; } static constexpr float VALUE = value(); }; template  struct LinearFunc { static float func(float x) { return GRAD::VALUE*x + CONST::VALUE; } }; int main() { // Y = 0.333 x + 0.2 // x=2, y=0.866 std::cout << " func(2) = " << LinearFunc, Float<1,5> > ::func(2) << std::endl; } 

Ref: http://code-slim-jim.blogspot.jp/2013/06/c11-no-floats-in-templates-wtf.html

Si no necesita que el doble sea una constante en tiempo de comstackción, puede pasarlo como un puntero:

 #include  extern const double kMyDouble = 0.1;; template  void writeDouble() { std::cout << *MyDouble << std::endl; } int main() { writeDouble<&kMyDouble>(); return 0; } 

Si solo quiere representar una precisión fija, puede usar una técnica como esta para convertir un parámetro flotante en un int.

Por ejemplo, una matriz con un factor de crecimiento de 1.75 podría crearse de la siguiente manera suponiendo 2 dígitos de precisión (dividir entre 100).

 template  class Array { public: static const float Factor; _Kind_ * Data; int Size; // ... void Resize() { _Kind_ * data = new _Kind_[(Size*Factor)+1]; // ... } } template const float Array<_kind_,_Factor_>::Factor = _Factor_/100; 

Si no te gusta la representación de 1.75 como 175 en la lista de argumentos de la plantilla, entonces siempre puedes envolverla en alguna macro.

 #define FloatToIntPrecision(f,p) (f*(10^p)) template  // ...