web-dev-qa-db-fra.com

Pourquoi ne puis-je pas utiliser la valeur flottante comme paramètre de modèle?

Lorsque j'essaie d'utiliser float comme paramètre de modèle, le compilateur pleure pour ce code, tandis que int fonctionne très bien.

Est-ce parce que je ne peux pas utiliser float comme paramètre de modèle?

#include<iostream>
using namespace std;

template <class T, T defaultValue>
class GenericClass
{
private:
    T value;
public:
    GenericClass()
    {
        value = defaultValue;
    }

    T returnVal()
    {
        return value;
    }
}; 


int main()
{
    GenericClass <int, 10> gcInteger;
    GenericClass < float, 4.6f> gcFlaot;

    cout << "\n sum of integer is "<<gcInteger.returnVal();
    cout << "\n sum of float is "<<gcFlaot.returnVal();

    return 0;       
}

Erreur:

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'

Je lis "Structures de données pour les programmeurs de jeux" par Ron Penton, l'auteur passe un float, mais quand je l'essaye ça ne marche pas semble pas compiler.

103
yokks

La norme C++ actuelle ne permet pas d'utiliser float (c'est-à-dire un nombre réel) ou des littéraux de chaîne de caractères comme paramètres de non-type de modèle. Vous pouvez bien sûr utiliser les float et char * types comme arguments normaux.

Peut-être que l'auteur utilise un compilateur qui ne respecte pas la norme actuelle?

33
anon

LA RÉPONSE SIMPLE

La norme n'autorise pas les virgules flottantes comme arguments de modèle non-type , qui peuvent être lus dans la section suivante de la norme C++ 11 ;

14.3.2/1 Arguments de non-type de modèle [temp.arg.nontype]

Un argument de modèle pour un paramètre de modèle non type et non modèle doit être l'un des éléments suivants:

  • pour un paramètre-modèle non-type de type intégral ou énumération, une expression constante convertie (5.19) du type du paramètre-modèle;

  • le nom d'un paramètre-modèle non type; ou

  • une expression constante (5.19) qui désigne l'adresse d'un objet avec une durée de stockage statique et une liaison externe ou interne ou une fonction avec une liaison externe ou interne, y compris des modèles de fonction et des identifiants de modèle de fonction mais à l'exclusion des membres de classe non statiques, exprimée (ignorant parenthèses) en tant qu'expression & id, sauf que le & peut être omis si le nom fait référence à une fonction ou à un tableau et doit être omis si le paramètre-modèle correspondant est une référence; ou

  • une expression constante qui évalue à une valeur de pointeur nulle (4.10); ou

  • une expression constante qui donne une valeur de pointeur de membre nul (4.11); ou

  • un pointeur sur un membre exprimé comme décrit en 5.3.1.


Mais .. mais .. POURQUOI!?

Cela est probablement dû au fait que les calculs en virgule flottante ne peuvent pas être représentés de manière exacte. Si cela était autorisé, cela pourrait/résulterait en un comportement erroné/bizarre en faisant quelque chose comme ça;

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

Nous voulions appeler la même fonction deux fois, mais cela pourrait ne pas être le cas car la représentation en virgule flottante des deux calculs n'est pas garantie d'être exactement identique.


Comment représenter des valeurs à virgule flottante comme arguments de modèle?

Avec C++11 vous pourriez écrire des expressions constantes assez avancées ( constexpr ) qui calculerait le numérateur/dénominateur d'un temps de compilation de valeur flottante, puis passerait ces deux en arguments entiers séparés.

N'oubliez pas de définir une sorte de seuil de sorte que les valeurs à virgule flottante proches les unes des autres produisent le même numérateur/dénominateur , sinon c'est un peu inutile car il donnera alors le même résultat mentionné précédemment comme raison de ne pas autoriser les valeurs à virgule flottante comme arguments de modèle non-type .

123

Juste pour fournir l'une des raisons pour lesquelles il s'agit d'une limitation (dans la norme actuelle au moins).

Lors de la mise en correspondance des spécialisations de modèle, le compilateur fait correspondre les arguments du modèle, y compris les arguments non-type.

De par leur nature même, les valeurs à virgule flottante ne sont pas exactes et leur implémentation n'est pas spécifiée par la norme C++. Par conséquent, il est difficile de décider quand deux arguments de type non flottant correspondent vraiment:

template <float f> void foo () ;

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

Ces expressions ne produisent pas nécessairement le même "motif binaire" et il ne serait donc pas possible de garantir qu'elles ont utilisé la même spécialisation - sans libellé spécial pour couvrir cela.

31
Richard Corden

En effet, vous ne pouvez pas utiliser de littéraux flottants comme paramètres de modèle. Voir section 14.1 ("Un paramètre de modèle non-type doit avoir l'un des types suivants (éventuellement qualifiés cv) ...") de la norme.

Vous pouvez utiliser une référence au flotteur comme paramètre de modèle:

template <class T, T const &defaultValue>
class GenericClass

.
.

float const c_four_point_six = 4.6; // at global scope

.
.

GenericClass < float, c_four_point_six> gcFlaot;
18
moonshadow

Enveloppez le ou les paramètres dans leur propre classe en tant que constexprs. En fait, cela est similaire à un trait car il paramètre la classe avec un ensemble de flottants.

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

puis créez un modèle en prenant le type de classe comme paramètre

  template <typename NUM, typename TUNING_PARAMS >
  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 ...
};

puis l'utiliser comme ça ...

int main (){
    PidController<float, MyParameters> controller;
    ...
    ...
}

Cela permet au compilateur de garantir qu'une seule instance du code est créée pour chaque instanciation de modèle avec le même pack de paramètres. Cela contourne tous les problèmes et vous pouvez utiliser des flottants et des doublons comme constexpr à l'intérieur de la classe basée sur des modèles.

6
Andrew Goedhart

Si vous êtes d'accord pour avoir une valeur par défaut fixe par type, vous pouvez créer un type pour le définir comme constante et le spécialiser au besoin.

template <typename T> struct MyTypeDefault { static const T value; };
template <typename T> const T MyTypeDefault<T>::value = T();
template <> struct MyTypeDefault<double> { static const double value; };
const double MyTypeDefault<double>::value = 1.0;

template <typename T>
class MyType {
  public:
    MyType() { value = MyTypeDefault<T>::value; }
  private:
    T value;
 };

Si vous avez C++ 11, vous pouvez utiliser constexpr lors de la définition de la valeur par défaut. Avec C++ 14, MyTypeDefault peut être une variable de modèle qui est un peu plus propre syntaxiquement.

//C++14
template <typename T> constexpr T MyTypeDefault = T();
template <> constexpr double MyTypeDefault<double> = 1.0;

template <typename T>
class MyType {
  private:
    T value = MyTypeDefault<T>;
 };
5
Matthew Fioravante

Vous pouvez toujours faire semblant ...

#include <iostream>

template <int NUM, int DEN>
struct Float
{
    static constexpr float value() { return (float)NUM / (float)DEN; }
    static constexpr float VALUE = value();
};

template <class GRAD, class CONST>
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,3>, Float<1,5> > ::func(2) << std::endl;
}

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

1
Ashley Smart

Si vous n'avez pas besoin que le double soit une constante au moment de la compilation, vous pouvez le passer comme pointeur:

#include <iostream>

extern const double kMyDouble = 0.1;;

template <const double* MyDouble>
void writeDouble() {
   std::cout << *MyDouble << std::endl; 
}

int main()
{
    writeDouble<&kMyDouble>();
   return 0;
}
1
user3233025

Les autres réponses donnent de bonnes raisons pour lesquelles vous ne voulez probablement pas de paramètres de modèle à virgule flottante, mais le vrai casse IMO est que l'égalité en utilisant '==' et l'égalité au niveau du bit ne sont pas les mêmes:

  1. -0.0 == 0.0, mais 0.0 et -0.0 ne sont pas égaux au niveau du bit

  2. NAN != NAN

Aucun des deux types d'égalité n'est un bon moyen d'annuler l'égalité de types: bien sûr, le point 2. fait en utilisant == invalide pour déterminer l'égalité de type. On pourrait utiliser à la place l'égalité au niveau du bit, mais alors x != y n'implique pas que MyClass<x> et MyClass<y> sont de types différents (par 2.), ce qui serait plutôt étrange.

0
Matthieu

Si vous souhaitez uniquement représenter une précision fixe, vous pouvez utiliser une technique comme celle-ci pour convertir un paramètre flottant en entier.

Par exemple, un tableau avec un facteur de croissance de 1,75 pourrait être créé comme suit en supposant 2 chiffres de précision (diviser par 100).

template <typename _Kind_, int _Factor_=175>
class Array
{
public:
    static const float Factor;
    _Kind_ * Data;
    int Size;

    // ...

    void Resize()
    {
         _Kind_ * data = new _Kind_[(Size*Factor)+1];

         // ...
    }
}

template<typename _Kind_, int _Factor_>
const float Array<_kind_,_Factor_>::Factor = _Factor_/100;

Si vous n'aimez pas la représentation de 1,75 comme 175 dans la liste des arguments du modèle, vous pouvez toujours l'envelopper dans une macro.

#define FloatToIntPrecision(f,p) (f*(10^p))

template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,2)>
// ...
0
jurujen