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


120

Lorsque j'essaie de l'utiliser floatcomme paramètre de modèle, le compilateur pleure ce code, alors qu'il intfonctionne correctement.

Est-ce parce que je ne peux pas utiliser floatcomme 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" de Ron Penton, l'auteur passe un float, mais quand je l'essaye, il ne semble pas se compiler.


1
L'auteur l'utilise-t-il vraiment floatcomme paramètre de modèle non type ? Dans quel chapitre est-ce?
K-ballo

1
Trouvé, il est à "Utiliser les valeurs comme paramètres de modèle" ...
K-ballo

Réponses:


37

Le standard C ++ actuel n'autorise pas float(c'est-à-dire un nombre réel) ou des chaînes de caractères littérales à être utilisées comme paramètres de modèle non-type . Vous pouvez bien sûr utiliser les types floatet char *comme arguments normaux.

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


8
Veuillez fournir un lien ou une copie de la section pertinente de la norme
thecoshman

2
@thecoshman la section pertinente de la norme + plus d'informations sont disponibles dans ma réponse (récemment publiée).
Filip Roséen - refp

1
En C ++ 11, il est à peu près possible d'utiliser une chaîne de caractères littérale comme paramètre de modèle non-type. Si votre modèle prend un pack de caractères template<char ...cs>, alors le littéral de chaîne peut être converti en un tel pack au moment de la compilation. Voici une démo sur ideone . (La démo est C ++ 14, mais il est facile de la porter en C ++ 11 - std::integer_sequencec'est la seule difficulté)
Aaron McDaid

Notez que vous pouvez utiliser char &*comme paramètre de modèle si vous définissez le littéral ailleurs. Fonctionne assez bien comme solution de contournement.
StenSoft

137

LA RÉPONSE SIMPLE

La norme n'autorise pas les points flottants en tant qu'arguments de modèle non de type , qui peuvent être lus dans la section suivante de la norme C ++ 11;

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

Un argument-modèle pour un paramètre-modèle non-type, non-modèle doit être l'un des suivants:

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

  • le nom d'un paramètre de 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 les modèles de fonction et les ID de modèle de fonction mais excluant les membres de classe non statiques, exprimée (en ignorant parenthèses) comme & id-expression, 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 de modèle correspondant est une référence; ou

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

  • une expression constante qui s'évalue à une valeur de pointeur de membre nul (4.11); ou

  • un pointeur vers 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 / entraînerait un comportement erroné / étrange en faisant quelque chose comme ceci;

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

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


Comment représenterais-je des valeurs à virgule flottante comme arguments de modèle?

Avec C++11vous pourriez écrire des expressions constantes assez avancées ( constexpr ) qui calculeraient le numérateur / dénominateur d'une valeur flottante lors de la compilation, puis passer ces deux comme arguments entiers séparés.

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


56
La solution C ++ 11 est <ratio>, décrite par le § 20.10 comme «Arithmétique rationnelle à la compilation». Ce qui va droit à votre exemple.
Potatoswatter

1
@Potatoswatter afaik il n'y a pas de méthode dans la STL pour convertir un flottant en numérateur / dénominateur en utilisant <ratio>?
Filip Roséen - refp

3
Cela ne donne pas vraiment d'explication convaincante. Le point entier de la virgule flottante est qu'il représente exactement les valeurs. Vous êtes libre de traiter les nombres que vous avez comme des approximations de quelque chose d'autre, et c'est souvent utile de le faire, mais les nombres eux-mêmes sont exacts.
tmyklebu

4
@ FilipRoséen-refp: Tous les nombres à virgule flottante sont exacts. L'arithmétique à virgule flottante est bien définie sur chaque cible que je connais. La plupart des opérations en virgule flottante produisent des résultats en virgule flottante. Je peux comprendre que le comité ne veuille pas forcer les implémenteurs de compilateurs à implémenter l'arithmétique à virgule flottante peut-être bizarre de la cible, mais je ne pense pas que "l'arithmétique est différente de l'arithmétique entière" soit une bonne raison pour interdire les arguments de modèle en virgule flottante. C'est une restriction arbitraire à la fin de la journée.
tmyklebu

5
@iheanyi: La norme dit-elle ce que 12345 * 12345c'est? (Il ne permet intmême les paramètres de modèle si elle ne précise pas la largeur d'un int signé ou si cette expression est UB.)
tmyklebu

34

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

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

De par leur nature même, les valeurs en virgule flottante ne sont pas exactes et leur implémentation n'est pas spécifiée par la norme C ++. En conséquence, il est difficile de décider quand deux arguments non type à virgule flottante 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 de bits" et il ne serait donc pas possible de garantir qu'elles utilisent la même spécialisation - sans une formulation spéciale pour couvrir cela.


16
C'est presque un argument pour bannir entièrement les flottants de la langue. Ou, au minimum, bannissez l' ==opérateur :-) Nous acceptons déjà cette inexactitude au moment de l'exécution, pourquoi pas aussi au moment de la compilation?
Aaron McDaid

3
D'accord avec @AaronMcDaid, ce n'est pas vraiment un argument. Vous devez donc être prudent dans la définition. Et alors? Tant que cela fonctionne pour les éléments que vous obtenez à partir des constantes, c'est déjà une véritable amélioration.
einpoklum

1
C ++ 20 autorise désormais le float (un autre type d'objet) en tant que paramètres de modèle non-type. Toujours C ++ 20 ne spécifie pas l'implémentation float. Cela montre qu'einpoklum et Aaron ont raison.
Andreas H.

20

En effet, vous ne pouvez pas utiliser de littéraux flottants comme paramètres de modèle. Voir la 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 flottant 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;

11
Vous pouvez. mais ça ne fait pas la même chose. Vous ne pouvez pas utiliser la référence comme constante de compilation.

12

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 utilisez-le 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 permet de contourner tous les problèmes et vous pouvez utiliser des flottants et se double de constexpr dans la classe basée sur un modèle.


5

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 une constante et le spécialiser selon vos besoins.

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 dont la syntaxe est un peu plus claire.

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

2

Les autres réponses donnent de bonnes raisons pour lesquelles vous ne voulez probablement pas de paramètres de modèle en virgule flottante, mais le vrai frein 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.0et -0.0ne sont pas égaux au niveau du bit

  2. NAN != NAN

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


1

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


3
A float! = Nombre rationnel. Les deux sont des idées très distinctes. L'une est calculée via une mantisse et un exposant, l'autre est, eh bien, un rationnel - toutes les valeurs représentables par un rationnel ne sont pas représentables par a float.
Richard J.Ross III

2
@ RichardJ.RossIII A floatest très certainement un nombre rationnel, mais il y a des floats qui ne sont pas représentables comme des rapports de deux ints. La mantisse est un entier, et l'exposant 2 ^ est un entier
Caleth

1

Si vous n'avez pas besoin que le double soit une constante de compilation, vous pouvez le transmettre en tant que 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;
}

Une référence est probablement meilleure, voir la réponse de
@moonshadow

1
Cela réduit-il réellement correctement au moment de la compilation?
Ant6n

1

À partir de C ++ 20, cela est possible .

Cela donne également la réponse à la question initiale:

Why can't I use float value as a template parameter?

Parce que personne ne l'a encore implémenté dans la norme. Il n'y a pas de raison fondamentale.

En C ++ 20, les paramètres de modèle non-type peuvent désormais être des flottants et même des objets de classe.

Il existe certaines exigences sur les objets de classe (ils doivent être de type littéral ) et remplir d'autres exigences pour exclure les cas pathologiques tels que l'opérateur défini par l'utilisateur == ( Détails ).

On peut même utiliser auto

template <auto Val>
struct Test {
};

struct A {};
static A aval;
Test<aval>  ta;
Test<A{}>  ta2;
Test<1.234>  tf;
Test<1U>  ti;

Notez que GCC 9 (et 10) implémente des paramètres de modèle de classe non-type, mais pas encore pour les flottants .


0

Si vous souhaitez uniquement représenter une précision fixe, vous pouvez utiliser une technique comme celle-ci pour convertir un paramètre float en un 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 d'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)>
// ...

il devrait en être ...::Factor = _Factor_/100.0;autrement, ce sera une division entière.
alfC
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.