Perché non posso utilizzare il valore float come parametro del modello?


120

Quando provo a utilizzare floatcome parametro del modello, il compilatore richiede questo codice, mentre intfunziona bene.

È perché non posso utilizzare floatcome parametro del modello?

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

Errore:

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'

Sto leggendo "Strutture dati per programmatori di giochi" di Ron Penton, l'autore passa un float, ma quando lo provo non sembra compilare.


1
L'autore utilizza davvero floatcome parametro di modello non di tipo ? In che capitolo si tratta?
K-ballo

1
Trovato, è in "Usare i valori come parametri del modello" ...
K-ballo

Risposte:


37

L'attuale standard C ++ non consente di utilizzare float(es. Numero reale) o stringhe di caratteri come parametri non di tipo del modello . Ovviamente puoi usare i tipi floate char *come normali argomenti.

Forse l'autore sta usando un compilatore che non segue lo standard attuale?


8
Fornire un collegamento o una copia della sezione pertinente dello standard
thecoshman

2
@thecoshman la sezione pertinente dello standard + ulteriori informazioni sono disponibili nella mia risposta (appena pubblicata).
Filip Roséen - refp

1
In C ++ 11, è quasi possibile utilizzare una stringa di caratteri letterale come parametro non di tipo del modello. Se il modello accetta un pacchetto di caratteri template<char ...cs>, la stringa letterale può essere convertita in tale pacchetto in fase di compilazione. Ecco una demo su ideone . (La demo è C ++ 14, ma è facile riportarla in C ++ 11 - std::integer_sequenceè l'unica difficoltà)
Aaron McDaid

Nota che puoi usare char &*come parametro del modello se definisci il letterale da qualche altra parte. Funziona abbastanza bene come soluzione alternativa.
StenSoft

137

LA RISPOSTA SEMPLICE

Lo standard non consente i punti mobili come argomenti di modello non di tipo , che possono essere letti nella sezione seguente dello standard C ++ 11;

14.3.2 / 1 Modello di argomenti non di tipo [temp.arg.nontype]

Un argomento-modello per un parametro modello non di tipo e non di modello deve essere uno dei seguenti:

  • per un parametro modello non di tipo di tipo integrale o di enumerazione, un'espressione costante convertita (5.19) del tipo di parametro modello;

  • il nome di un parametro di modello non di tipo; o

  • un'espressione costante (5.19) che designa l'indirizzo di un oggetto con durata di memorizzazione statica e collegamento esterno o interno o una funzione con collegamento esterno o interno, inclusi modelli di funzione e ID modello di funzione ma esclusi membri di classe non statici, espressa (ignorando parentesi) come & id-espressione, eccetto che la & può essere omessa se il nome si riferisce a una funzione o array e deve essere omessa se il corrispondente parametro-template è un riferimento; o

  • un'espressione costante che restituisce un valore di puntatore nullo (4.10); o

  • un'espressione costante che restituisce un valore del puntatore a un membro nullo (4.11); o

  • un puntatore a membro espresso come descritto in 5.3.1.


Ma .. ma .. PERCHÉ !?

Probabilmente è dovuto al fatto che i calcoli in virgola mobile non possono essere rappresentati in modo esatto. Se fosse consentito, potrebbe / risulterebbe in un comportamento errato / strano quando si fa qualcosa del genere;

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

Intendevamo chiamare la stessa funzione due volte, ma potrebbe non essere così poiché non è garantito che la rappresentazione in virgola mobile dei due calcoli sia esattamente la stessa.


Come rappresenterei i valori in virgola mobile come argomenti del modello?

Con C++11si potrebbero scrivere alcune espressioni costanti piuttosto avanzate ( constexpr ) che calcolerebbero il numeratore / denominatore di un valore mobile durante la compilazione e quindi passerebbero questi due come argomenti interi separati.

Ricorda di definire una sorta di soglia in modo che i valori in virgola mobile vicini l'uno all'altro producano lo stesso numeratore / denominatore , altrimenti è un po 'inutile poiché produrrà lo stesso risultato precedentemente menzionato come motivo per non consentire valori in virgola mobile come non di tipo argomenti del modello .


56
La soluzione C ++ 11 è <ratio>, descritta da §20.10 come "Aritmetica razionale in fase di compilazione". Il che taglia dritto al tuo esempio.
Potatoswatter

1
@ Potatoswatter afaik non c'è alcun metodo nell'STL per convertire un float in numeratore / denominatore usando <ratio>?
Filip Roséen - refp

3
Questo non fornisce davvero una spiegazione convincente. L'intero punto di virgola mobile è che rappresenta esattamente i valori. Sei libero di trattare i numeri che hai come approssimazioni di qualcos'altro, ed è spesso utile farlo, ma i numeri stessi sono esatti.
tmyklebu

4
@ FilipRoséen-refp: tutti i numeri in virgola mobile sono esatti. L'aritmetica in virgola mobile è ben definita su ogni obiettivo che conosco. La maggior parte delle operazioni in virgola mobile produce risultati in virgola mobile. Posso apprezzare il comitato che non vuole forzare gli implementatori del compilatore a implementare l'aritmetica in virgola mobile possibilmente bizzarra del target, ma non credo che "l'aritmetica è diversa dall'aritmetica intera" sia una buona ragione per proibire gli argomenti del modello a virgola mobile. È una restrizione arbitraria alla fine della giornata.
tmyklebu

5
@ iheanyi: Lo standard dice cosa 12345 * 12345è? (Si fa permettere intparametri di modello, anche se non specifica la larghezza di un int firmato o se tale espressione è UB.)
tmyklebu

34

Solo per fornire uno dei motivi per cui questa è una limitazione (almeno nello standard attuale).

Quando si confrontano le specializzazioni del modello, il compilatore corrisponde agli argomenti del modello, inclusi gli argomenti non di tipo.

Per loro stessa natura, i valori in virgola mobile non sono esatti e la loro implementazione non è specificata dallo standard C ++. Di conseguenza, è difficile decidere quando due argomenti non di tipo in virgola mobile corrispondono realmente:

template <float f> void foo () ;

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

Queste espressioni non producono necessariamente lo stesso "schema di bit" e quindi non sarebbe possibile garantire che usassero la stessa specializzazione - senza una formulazione speciale per coprirlo.


16
Questo è quasi un argomento per vietare completamente i float dalla lingua. O, come minimo, bannare l' ==operatore :-) Accettiamo già questa imprecisione in fase di esecuzione, perché non anche in fase di compilazione?
Aaron McDaid

3
D'accordo con @AaronMcDaid, questo non è un grosso argomento. Quindi devi stare attento nella definizione. E allora? Finché funziona per le cose che ottieni dalle costanti, è già un bel miglioramento.
einpoklum

1
C ++ 20 ora consente float (un altro tipo di oggetto) come parametri di modello non di tipo. Ancora C ++ 20 non specifica l'implementazione float. Questo dimostra che einpoklum e Aaron hanno ragione.
Andreas H.

20

In effetti, non è possibile utilizzare valori letterali float come parametri del modello. Vedere la sezione 14.1 ("Un parametro di modello non di tipo deve avere uno dei seguenti tipi (facoltativamente qualificati per cv) ...") dello standard.

È possibile utilizzare un riferimento al float come parametro del modello:

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
Puoi. ma non fa la stessa cosa. Non è possibile utilizzare il riferimento come costante del tempo di compilazione.

12

Avvolgi i parametri nella loro classe come constexprs. Effettivamente questo è simile a un tratto poiché parametrizza la classe con un insieme di float.

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

e quindi creare un modello prendendo il tipo di classe come parametro

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

e poi usalo in questo modo ...

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

Ciò consente al compilatore di garantire che venga creata solo una singola istanza del codice per ogni istanza di modello con lo stesso pacchetto di parametri. Questo aggira tutti i problemi e sei in grado di utilizzare float e raddoppia come constexpr all'interno della classe basata su modelli.


5

Se sei a posto per avere un valore predefinito fisso per tipo, puoi creare un tipo per definirlo come costante e specializzarlo secondo necessità.

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

Se hai C ++ 11 puoi usare constexpr quando definisci il valore predefinito. Con C ++ 14, MyTypeDefault può essere una variabile di modello che è sintatticamente un po 'più pulita.

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

Le altre risposte forniscono buone ragioni per cui probabilmente non vuoi i parametri del modello in virgola mobile, ma il vero problema IMO è che l'uguaglianza usando '==' e l'uguaglianza bit per bit non sono la stessa cosa:

  1. -0.0 == 0.0, ma 0.0e -0.0non sono uguali a livello di bit

  2. NAN != NAN

Nessuno dei due tipi di uguaglianza è un buon cancidato per l'uguaglianza di tipo: ovviamente, il punto 2. rende l'uso ==non valido per determinare l'uguaglianza di tipo. Si potrebbe invece usare l'uguaglianza bit per bit, ma poi x != ynon lo implica MyClass<x>e MyClass<y>sono tipi diversi (per 2.), il che sarebbe piuttosto strano.


1

Puoi sempre fingere ...

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

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


3
A float! = Numero razionale. Le due sono idee molto separate. Uno è calcolato tramite una mantissa e un esponente, l'altro è, beh, un razionale - non tutti i valori rappresentabili da un razionale sono rappresentabili da a float.
Richard J. Ross III,

2
@ RichardJ.RossIII A floatè decisamente un numero razionale, ma ci sono floats che non sono rappresentabili come rapporti di due ints. La mantissa è un numero intero e il 2 ^ esponente è un numero intero
Caleth,

1

Se non hai bisogno che il double sia una costante del tempo di compilazione, puoi passarlo come puntatore:

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

Un riferimento è probabilmente migliore, vedi la risposta di
@moonshadow

1
Questo effettivamente si riduce correttamente in fase di compilazione?
Ant6n

1

A partire da C ++ 20 questo è possibile .

Questo dà anche la risposta alla domanda originale:

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

Perché nessuno lo ha ancora implementato nello standard. Non c'è una ragione fondamentale.

In C ++ 20 i parametri del modello non di tipo ora possono essere float e persino oggetti di classe.

Ci sono alcuni requisiti sugli oggetti di classe (devono essere di tipo letterale ) e soddisfano altri requisiti per escludere i casi patologici come operatore definito dall'utente == ( Dettagli ).

Possiamo anche usare auto

template <auto Val>
struct Test {
};

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

Notare che GCC 9 (e 10) implementa parametri di template non di tipo di classe, ma non ancora per i float .


0

Se vuoi rappresentare solo una precisione fissa, puoi usare una tecnica come questa per convertire un parametro float in un int.

Ad esempio, un array con un fattore di crescita di 1,75 potrebbe essere creato come segue assumendo 2 cifre di precisione (dividere per 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;

Se non ti piace la rappresentazione di 1,75 come 175 nell'elenco degli argomenti del modello, puoi sempre racchiuderla in qualche macro.

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

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

dovrebbe essere ...::Factor = _Factor_/100.0;altrimenti sarà una divisione intera.
alfC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.