Warum kann ich den Float-Wert nicht als Vorlagenparameter verwenden?

120

Wenn ich versuche, floatals Vorlagenparameter zu verwenden, schreit der Compiler nach diesem Code, während er inteinwandfrei funktioniert.

Liegt es daran, dass ich nicht floatals Vorlagenparameter verwenden kann?

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

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'

Ich lese "Data Structures for Game Programmers" von Ron Penton, der Autor gibt ein float, aber wenn ich es versuche, scheint es nicht zu kompilieren.

Yokks
quelle
1
Verwendet der Autor wirklich floateinen Nicht-Typ-Vorlagenparameter ? In welchem ​​Kapitel ist das?
K-Ballo
1
Gefunden, es ist bei "Verwenden von Werten als Vorlagenparameter" ...
K-Ballo

Antworten:

37

Der aktuelle C ++ - Standard erlaubt nicht, dass floatLiterale (dh reelle Zahlen) oder Zeichenkettenliterale als nicht typisierte Vorlagenparameter verwendet werden . Sie können die Typen floatund natürlich char *als normale Argumente verwenden.

Vielleicht verwendet der Autor einen Compiler, der nicht dem aktuellen Standard entspricht?

Filip Roséen - refp
quelle
8
Bitte geben Sie einen Link oder eine Kopie des relevanten Abschnitts aus dem Standard an
thecoshman
2
@thecoshman Der relevante Abschnitt des Standards + weitere Informationen finden Sie in meiner (neu veröffentlichten) Antwort.
Filip Roséen - Refp
1
In C ++ 11 ist es nahezu möglich, ein Zeichenfolgenliteral als Nicht-Typ-Parameter für Vorlagen zu verwenden. Wenn Ihre Vorlage ein Zeichenpaket enthält template<char ...cs>, kann das Zeichenfolgenliteral zur Kompilierungszeit in ein solches Paket konvertiert werden. Hier ist eine Demo zu ideone . (Demo ist C ++ 14, aber es ist einfach, es zurück auf C ++ 11 zu portieren - std::integer_sequenceist die einzige Schwierigkeit)
Aaron McDaid
Beachten Sie, dass Sie char &*als Vorlagenparameter verwenden können, wenn Sie das Literal an einer anderen Stelle definieren. Funktioniert ziemlich gut als Workaround.
StenSoft
137

DIE EINFACHE ANTWORT

Der Standard erlaubt keine Gleitkommazahlen als nicht typisierte Vorlagenargumente , über die im folgenden Abschnitt des C ++ 11-Standards gelesen werden kann.

14.3.2 / 1 Argumente ohne Typ [temp.arg.nontype]

Ein Template-Argument für einen Nicht-Typ-Nicht-Template-Template-Parameter muss eines der folgenden sein:

  • für einen Nicht-Typ-Vorlagenparameter vom Integral- oder Aufzählungstyp ein konvertierter konstanter Ausdruck (5.19) vom Typ des Vorlagenparameters;

  • der Name eines Nicht-Typ-Vorlagenparameters; oder

  • Ein konstanter Ausdruck (5.19), der die Adresse eines Objekts mit statischer Speicherdauer und externer oder interner Verknüpfung oder eine Funktion mit externer oder interner Verknüpfung bezeichnet, einschließlich Funktionsvorlagen und Funktionsvorlagen-IDs, jedoch ohne nicht statische Klassenmitglieder, ausgedrückt (ignoriert) Klammern) als & id-Ausdruck, außer dass das & weggelassen werden kann, wenn sich der Name auf eine Funktion oder ein Array bezieht, und weggelassen werden soll, wenn der entsprechende Template-Parameter eine Referenz ist; oder

  • ein konstanter Ausdruck, der einen Nullzeigerwert (4.10) ergibt; oder

  • ein konstanter Ausdruck, der einen Nullelementzeigerwert (4.11) ergibt; oder

  • ein Zeiger auf ein Mitglied, ausgedrückt wie in 5.3.1 beschrieben.


Aber .. aber .. WARUM!?

Dies liegt wahrscheinlich daran, dass Gleitkommaberechnungen nicht exakt dargestellt werden können. Wenn es erlaubt wäre, könnte / würde es zu fehlerhaftem / seltsamem Verhalten führen, wenn man so etwas tut;

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

Wir wollten dieselbe Funktion zweimal aufrufen, dies ist jedoch möglicherweise nicht der Fall, da die Gleitkommadarstellung der beiden Berechnungen nicht garantiert exakt gleich ist.


Wie würde ich Gleitkommawerte als Vorlagenargumente darstellen?

Mit C++11könnten Sie einige ziemlich fortgeschrittene Konstantenausdrücke ( constexpr ) schreiben , die den Zähler / Nenner einer Kompilierungszeit mit schwebendem Wert berechnen und diese beiden dann als separate ganzzahlige Argumente übergeben.

Denken Sie daran, eine Art Schwellenwert zu definieren, damit Gleitkommawerte nahe beieinander denselben Zähler / Nenner ergeben . Andernfalls ist dies irgendwie sinnlos, da dann dasselbe Ergebnis erzielt wird, das zuvor als Grund genannt wurde, Gleitkommawerte nicht als Nicht-Typ zuzulassen Vorlagenargumente .

Filip Roséen - refp
quelle
56
Die C ++ 11-Lösung wird <ratio>in §20.10 als "Rationale Arithmetik zur Kompilierungszeit" beschrieben. Was genau zu Ihrem Beispiel passt.
Potatoswatter
1
@Potatoswatter afaik gibt es keine Methode in der STL, um einen Float mit <ratio>? In Zähler / Nenner umzuwandeln ?
Filip Roséen - Refp
3
Dies gibt keine wirklich überzeugende Erklärung. Der springende Punkt beim Gleitkomma ist, dass er Werte genau darstellt. Es steht Ihnen frei, die Zahlen, die Sie haben, als Annäherungen an etwas anderes zu behandeln, und dies ist oft nützlich, aber die Zahlen selbst sind genau.
tmyklebu
4
@ FilipRoséen-refp: Alle Gleitkommazahlen sind genau. Die Gleitkomma-Arithmetik ist für jedes mir bekannte Ziel genau definiert. Die meisten Gleitkommaoperationen führen zu Gleitkommaergebnissen. Ich kann es begrüßen, dass das Komitee Compiler-Implementierer nicht zwingen möchte, die möglicherweise bizarre Gleitkomma-Arithmetik des Ziels zu implementieren, aber ich glaube nicht, dass "die Arithmetik unterscheidet sich von der Ganzzahl-Arithmetik" ein guter Grund ist, Gleitkomma-Vorlagenargumente zu verbieten. Es ist eine willkürliche Einschränkung am Ende des Tages.
tmyklebu
5
@iheanyi: Sagt der Standard was 12345 * 12345ist? (Es tut erlaubt intsogar Template - Parameter , obwohl es nicht die Breite eines signed int angeben ist oder ob dieser Ausdruck ist UB.)
tmyklebu
34

Nur um einen der Gründe anzugeben, warum dies eine Einschränkung ist (zumindest im aktuellen Standard).

Beim Abgleichen von Vorlagenspezialisierungen stimmt der Compiler mit den Vorlagenargumenten überein, einschließlich nicht typisierter Argumente.

Gleitkommawerte sind naturgemäß nicht genau und ihre Implementierung ist nicht im C ++ - Standard festgelegt. Infolgedessen ist es schwierig zu entscheiden, wann zwei Gleitkomma-Argumente ohne Typ wirklich übereinstimmen:

template <float f> void foo () ;

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

Diese Ausdrücke erzeugen nicht notwendigerweise das gleiche "Bitmuster", so dass nicht garantiert werden kann, dass sie dieselbe Spezialisierung verwendet haben - ohne spezielle Formulierung, um dies abzudecken.

Richard Corden
quelle
16
Dies ist fast ein Argument, um Floats vollständig aus der Sprache zu verbannen. Oder zumindest den ==Operator verbieten :-) Wir akzeptieren diese Ungenauigkeit bereits zur Laufzeit, warum nicht auch zur Kompilierungszeit?
Aaron McDaid
3
Stimmen Sie mit @AaronMcDaid überein, dies ist kein großes Argument. Sie müssen also bei der Definition vorsichtig sein. Na und? Solange es für Dinge funktioniert, die Sie von Konstanten erhalten, ist es bereits eine ziemliche Verbesserung.
Einpoklum
1
C ++ 20 ermöglicht jetzt float (einen anderen Objekttyp) als nicht typisierte Vorlagenparameter. Dennoch gibt C ++ 20 die Float-Implementierung nicht an. Dies zeigt, dass Einpoklum und Aaron einen Punkt haben.
Andreas H.
20

In der Tat können Sie keine Float-Literale als Vorlagenparameter verwenden. Siehe Abschnitt 14.1 ("Ein Vorlagenparameter ohne Typ muss einen der folgenden (optional lebenslaufqualifizierten) Typen haben ...") der Norm.

Sie können einen Verweis auf den Float als Vorlagenparameter verwenden:

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;
Mondschatten
quelle
11
Sie können. aber es macht nicht das gleiche. Sie können die Referenz nicht als Kompilierungszeitkonstante verwenden.
12

Wickeln Sie die Parameter in ihre eigene Klasse als constexprs ein. Tatsächlich ähnelt dies einem Merkmal, da es die Klasse mit einer Reihe von Floats parametrisiert.

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

Erstellen Sie anschließend eine Vorlage, die den Klassentyp als Parameter verwendet

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

und dann benutze es so ...

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

Auf diese Weise kann der Compiler sicherstellen, dass für jede Vorlageninstanziierung mit demselben Parameterpaket nur eine einzige Instanz des Codes erstellt wird. Damit werden alle Probleme umgangen, und Sie können Floats und Doubles als Constexpr innerhalb der Vorlagenklasse verwenden.

Andrew Goedhart
quelle
5

Wenn Sie einen festen Standardwert pro Typ festlegen möchten, können Sie einen Typ erstellen, um ihn als Konstante zu definieren und nach Bedarf zu spezialisieren.

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

Wenn Sie über C ++ 11 verfügen, können Sie constexpr verwenden, um den Standardwert zu definieren. Mit C ++ 14 kann MyTypeDefault eine Vorlagenvariable sein, die syntaktisch etwas sauberer ist.

//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>;
 };
Matthew Fioravante
quelle
2

Die anderen Antworten geben gute Gründe an, warum Sie wahrscheinlich keine Gleitkomma-Vorlagenparameter möchten, aber der eigentliche Deal-Braker IMO ist, dass Gleichheit mit '==' und bitweise Gleichheit nicht dasselbe sind:

  1. -0.0 == 0.0, aber 0.0und -0.0sind nicht bitweise gleich

  2. NAN != NAN

Keine der beiden Arten von Gleichheit ist ein guter Kandidat für die Typgleichheit: Natürlich macht Punkt 2 die Verwendung ==für die Bestimmung der Typgleichheit ungültig. Man könnte stattdessen bitweise Gleichheit verwenden, x != yimpliziert dies aber nicht MyClass<x>und MyClass<y>es handelt sich um verschiedene Typen (um 2), was ziemlich seltsam wäre.

Matthieu
quelle
1

Sie können es immer vortäuschen ...

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

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

Ashley Smart
quelle
3
A float! = Rationale Zahl. Die beiden sind sehr unterschiedliche Ideen. Einer wird über eine Mantisse und einen Exponenten berechnet, der andere ist ein Rational - nicht jeder Wert, der durch einen Rational darstellbar ist, ist durch a darstellbar float.
Richard J. Ross III
2
@ RichardJ.RossIII A floatist definitiv eine rationale Zahl, aber es gibt floats, die nicht als Verhältnisse von zwei ints dargestellt werden können. Die Mantisse ist eine
ganze Zahl
1

Wenn das Double keine Konstante zur Kompilierungszeit sein muss, können Sie es als Zeiger übergeben:

#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;
}
user3233025
quelle
Eine Referenz ist wahrscheinlich besser, siehe @moonshadows Antwort
einpoklum
1
Reduziert sich dies beim Kompilieren tatsächlich richtig?
Ant6n
1

Ab C ++ 20 ist dies möglich .

Dies gibt auch die Antwort auf die ursprüngliche Frage:

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

Weil es noch niemand im Standard implementiert hat. Es gibt keinen fundamentalen Grund.

In C ++ 20 können nicht typisierte Vorlagenparameter jetzt Floats und sogar Klassenobjekte sein.

Es gibt einige Anforderungen an Klassenobjekte (sie müssen vom Typ Literal sein ) und einige andere Anforderungen, um die pathologischen Fälle auszuschließen, z. B. den benutzerdefinierten Operator == ( Details ).

Wir können sogar verwenden auto

template <auto Val>
struct Test {
};

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

Beachten Sie, dass GCC 9 (und 10) Vorlagenparameter vom Typ Nicht-Typ implementiert, jedoch noch nicht für Floats .

Andreas H.
quelle
0

Wenn Sie nur eine feste Genauigkeit darstellen möchten, können Sie mit einer solchen Technik einen float-Parameter in einen int konvertieren.

Zum Beispiel könnte ein Array mit einem Wachstumsfaktor von 1,75 wie folgt erstellt werden, wobei eine Genauigkeit von 2 Stellen angenommen wird (dividiert durch 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;

Wenn Ihnen die Darstellung von 1,75 als 175 in der Liste der Vorlagenargumente nicht gefällt, können Sie sie jederzeit in ein Makro einschließen.

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

template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,2)>
// ...
jurujen
quelle
es sollte sein , ...::Factor = _Factor_/100.0;sonst wird es Integer - Division sein.
AlfC