Por que não posso usar o valor flutuante como um parâmetro de modelo?

Quando tento usar float como um parâmetro de modelo, o compilador chora para esse código, enquanto int funciona bem.

É porque eu não posso usar float como um parâmetro de modelo?

 #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; } 

Erro:

 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' 

Eu estou lendo “Estruturas de Dados para Programadores de Jogos” de Ron Penton, o autor passa um float , mas quando eu tento não parece compilar.

O padrão C ++ atual não permite que literais float (ou seja, números reais) ou cadeias de caracteres de caracteres sejam usados ​​como parâmetros não-tipo de modelo . É claro que você pode usar os tipos float e char * como argumentos normais.

Talvez o autor esteja usando um compilador que não segue o padrão atual?

A RESPOSTA SIMPLES

O padrão não permite pontos flutuantes como argumentos modelo não-tipo , que podem ser lidos na seção seguinte do padrão C ++ 11;

14.3.2 / 1 Argumentos não tipificados do modelo [temp.arg.nontype]

Um argumento modelo para um parâmetro de modelo não tipográfico e não modelo deve ser um dos seguintes:

  • para um parâmetro de modelo não-tipo de integral ou tipo de enumeração, uma expressão constante convertida (5.19) do tipo do parâmetro de modelo;

  • o nome de um parâmetro de modelo sem tipo; ou

  • uma expressão constante (5.19) que designa o endereço de um object com duração de armazenamento estático e binding externa ou interna ou uma function com binding externa ou interna, incluindo modelos de function e ids de modelo de function, mas excluindo membros de class não estáticos, expressos (ignorando parênteses) como expressão & id, exceto que o & pode ser omitido se o nome se referir a uma function ou matriz e deve ser omitido se o parâmetro de modelo correspondente for uma referência; ou

  • uma expressão constante que avalia um valor de ponteiro nulo (4.10); ou

  • uma expressão constante que avalia um valor de ponteiro de membro nulo (4.11); ou

  • um ponteiro para o membro expresso como descrito em 5.3.1.


Mas .. mas .. POR QUE?

É provavelmente devido ao fato de que os cálculos de ponto flutuante não podem ser representados de uma maneira exata. Se fosse permitido, poderia / resultaria em comportamento errôneo / estranho ao fazer algo como isso;

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

Pretendemos chamar a mesma function duas vezes, mas isso pode não ser o caso, pois a representação de ponto flutuante dos dois cálculos não é garantida como sendo exatamente a mesma.


Como eu representaria valores de ponto flutuante como argumentos de modelo?

Com o C++11 você poderia escrever algumas expressões constantes bastante avançadas ( constexpr ) que calculariam o numerador / denominador de um tempo de compilation do valor flutuante e então passariam esses dois como argumentos inteiros separados.

Lembre-se de definir algum tipo de limiar para que valores de ponto flutuante próximos um ao outro produzam o mesmo numerador / denominador , caso contrário é inútil, pois produzirá o mesmo resultado mencionado anteriormente como uma razão para não permitir valores de ponto flutuante como não-tipo argumentos modelo .

Apenas para fornecer uma das razões pelas quais isso é uma limitação (no padrão atual, pelo menos).

Ao combinar especializações de modelo, o compilador corresponde aos argumentos do modelo, incluindo argumentos não-tipo.

Por sua própria natureza, valores de ponto flutuante não são exatos e sua implementação não é especificada pelo padrão C ++. Como resultado, é difícil decidir quando dois argumentos de tipo ponto flutuante realmente correspondem:

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

Essas expressões não produzem necessariamente o mesmo “padrão de bits” e, portanto, não seria possível garantir que elas usassem a mesma especialização – sem uma redação especial para abordar isso.

De fato, você não pode usar literais flutuantes como parâmetros de modelo. Consulte a seção 14.1 (“Um parâmetro de modelo não-tipo deve ter um dos seguintes tipos (opcionalmente qualificados para cv) …”) do padrão.

Você pode usar uma referência ao float como um parâmetro de modelo:

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

Envolva o (s) parâmetro (s) em sua própria class como constexprs. Efetivamente isso é semelhante a um traço, uma vez que ele parametriza a class com um conjunto de flutuadores.

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

e, em seguida, criar um modelo tomando o tipo de class como um 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 ... }; 

e depois usá-lo assim …

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

Isso permite que o compilador garanta que apenas uma única instância do código seja criada para cada instanciação de modelo com o mesmo pacote de parâmetros. Isso contorna todos os problemas e você pode usar floats e doubles como constexpr dentro da class de templates.

Se você está ok para ter um padrão fixo por tipo, você pode criar um tipo para defini-lo como uma constante e especializá-lo conforme necessário.

 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; }; 

Se você tiver o C ++ 11, poderá usar constexpr ao definir o valor padrão. Com o C ++ 14, MyTypeDefault pode ser uma variável de template que é um pouco mais limpa sintaticamente.

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

Você sempre pode 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

Se você não precisa que o dobro seja uma constante de tempo de compilation, você pode passá-lo como um ponteiro:

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

Se você quer apenas representar uma precisão fixa, então você pode usar uma técnica como essa para converter um parâmetro float em um int.

Por exemplo, uma matriz com um fator de crescimento de 1,75 poderia ser criada da seguinte maneira, assumindo 2 dígitos de precisão (dividir por 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; 

Se você não gostar da representação de 1,75 como 175 na lista de argumentos do modelo, poderá sempre envolvê-la em alguma macro.

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