Dlaczego nie mogę użyć wartości zmiennoprzecinkowej jako parametru szablonu?


120

Kiedy próbuję użyć floatjako parametru szablonu, kompilator woła o ten kod, podczas gdy intdziała dobrze.

Czy to dlatego, że nie mogę użyć floatjako parametru szablonu?

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

Błąd:

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'

Czytam „Struktury danych dla programistów gier” Rona Pentona, autor podaje float, ale kiedy próbuję, nie wydaje się, aby się kompilował.


1
Czy autor naprawdę używa floatjako parametru szablonu innego niż typ ? W którym to rozdziale?
K-ballo

1
Znalazłem go, jest to „Używanie wartości jako parametrów szablonu” ...
K-ballo

Odpowiedzi:


37

Obecny standard C ++ nie zezwala float(tj. Liczby rzeczywistej) lub literałów ciągu znaków na używanie jako parametrów innych niż typowe dla szablonu . Możesz oczywiście używać typów floati char *jako zwykłych argumentów.

Być może autor używa kompilatora, który nie jest zgodny z obecnym standardem?


8
Proszę podać link lub kopię odpowiedniej sekcji ze standardu
thecoshman

2
@thecoshman odpowiednia sekcja normy + więcej informacji jest dostępna w mojej (nowo opublikowanej) odpowiedzi.
Filip Roséen - odniesienie

1
W C ++ 11 jest prawie możliwe użycie literału ciągu znaków jako parametru innego niż typ szablonu. Jeśli Twój szablon przyjmuje pakiet znaków template<char ...cs>, to literał ciągu można przekształcić w taki pakiet w czasie kompilacji. Oto demo ideone . (Demo to C ++ 14, ale łatwo jest go przenieść z powrotem do C ++ 11 - std::integer_sequenceto jedyna trudność)
Aaron McDaid

Zauważ, że możesz użyć char &*jako parametru szablonu, jeśli zdefiniujesz literał w innym miejscu. Działa całkiem nieźle jako obejście.
StenSoft

137

PROSTA ODPOWIEDŹ

Standard nie dopuszcza zmiennoprzecinkowych jako argumentów szablonowych innych niż typowe , o których można przeczytać w poniższej sekcji standardu C ++ 11;

14.3.2 / 1 Argumenty inne niż typowe w szablonie [temp.arg.nontype]

Argument szablonowy dla parametru szablonowego innego niż typowy i inny niż szablonowy to:

  • dla nietypowego parametru-szablonu o typie całkowitym lub wyliczeniowym, przekształcone wyrażenie stałe (5.19) typu parametr-szablon;

  • nazwa parametru szablonu innego niż typ; lub

  • wyrażenie stałe (5.19), które wyznacza adres obiektu ze statycznym czasem trwania i powiązaniem zewnętrznym lub wewnętrznym lub funkcją z połączeniem zewnętrznym lub wewnętrznym, w tym szablony funkcji i identyfikatory szablonów funkcji, ale z wyłączeniem niestatycznych składowych klas, wyrażone (ignorując nawiasy) jako & id-wyrażenie, z tą różnicą, że & można pominąć, jeśli nazwa odnosi się do funkcji lub tablicy, i należy go pominąć, jeśli odpowiedni parametr szablonu jest odniesieniem; lub

  • wyrażenie stałe, którego wynikiem jest wartość wskaźnika zerowego (4.10); lub

  • wyrażenie stałe, którego wynikiem jest pusta wartość wskaźnika elementu członkowskiego (4.11); lub

  • wskaźnik do pręta wyrażony jak opisano w 5.3.1.


Ale… ale… DLACZEGO !?

Wynika to prawdopodobnie z faktu, że obliczeń zmiennoprzecinkowych nie można przedstawić w dokładny sposób. Gdyby było dozwolone, mogłoby / spowodowałoby błędne / dziwne zachowanie podczas robienia czegoś takiego;

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

Chcieliśmy wywołać tę samą funkcję dwa razy, ale może tak nie być, ponieważ nie ma gwarancji, że reprezentacja zmiennoprzecinkowa dwóch obliczeń będzie dokładnie taka sama.


Jak przedstawiłbym wartości zmiennoprzecinkowe jako argumenty szablonu?

Ze C++11można napisać całkiem zaawansowanych stałym wyrażeń ( constexpr ), które mogłyby obliczyć licznik / mianownik pływającej czasie wartość kompilacji, a następnie przekazać te dwa odsunięte całkowitych.

Pamiętaj, aby zdefiniować jakiś rodzaj progu, aby wartości zmiennoprzecinkowe blisko siebie dawały ten sam licznik / mianownik , w przeciwnym razie jest to trochę bezcelowe, ponieważ wtedy da ten sam wynik wspomniany wcześniej jako powód, aby nie zezwalać na wartości zmiennoprzecinkowe jako inne niż typ argumenty szablonu .


56
Rozwiązanie C ++ 11 jest <ratio>opisane w §20.10 jako „arytmetyka racjonalna czasu kompilacji”. Co dobrze pasuje do twojego przykładu.
Potatoswatter

1
@Potatoswatter afaik W pliku STL nie ma żadnej metody konwersji wartości zmiennoprzecinkowej na licznik / mianownik przy użyciu <ratio>?
Filip Roséen - patrz

3
To naprawdę nie daje przekonującego wyjaśnienia. Cały punkt zmiennoprzecinkowy polega na tym, że dokładnie reprezentuje wartości. Możesz traktować liczby, które masz, jako przybliżenia do czegoś innego i często jest to przydatne, ale same liczby są dokładne.
tmyklebu

4
@ FilipRoséen-refp: Wszystkie liczby zmiennoprzecinkowe są dokładne. Arytmetyka zmiennoprzecinkowa jest dobrze zdefiniowana dla każdego celu, jaki znam. Większość operacji zmiennoprzecinkowych daje wyniki zmiennoprzecinkowe. Doceniam, że komitet nie chce zmuszać twórców kompilatorów do implementacji prawdopodobnie dziwacznej arytmetyki zmiennoprzecinkowej celu, ale nie uważam, że „arytmetyka różni się od arytmetyki całkowitoliczbowej” jest dobrym powodem, aby zabronić używania argumentów zmiennoprzecinkowych szablonów. To arbitralne ograniczenie na koniec dnia.
tmyklebu

5
@iheanyi: Czy norma mówi, co 12345 * 12345to jest? (To nie pozwalają intparametrów szablonu, mimo że nie określa szerokość podpisanego int, czy też, że wyrażenie jest UB.)
tmyklebu

34

Wystarczy podać jeden z powodów, dla których jest to ograniczenie (przynajmniej w obecnym standardzie).

Podczas dopasowywania specjalizacji szablonu kompilator dopasowuje argumenty szablonu, w tym argumenty inne niż typowe.

Ze swej natury wartości zmiennoprzecinkowe nie są dokładne, a ich implementacja nie jest określona przez standard C ++. W rezultacie trudno jest zdecydować, kiedy dwa argumenty zmiennoprzecinkowe nietypowe naprawdę pasują do siebie:

template <float f> void foo () ;

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

Wyrażenia te niekoniecznie generują ten sam „wzorzec bitowy”, więc nie byłoby możliwe zagwarantowanie, że używają tej samej specjalizacji - bez specjalnego sformułowania, które to obejmuje.


16
To prawie argument za całkowitym zakazem wypływów z języka. Lub przynajmniej zbanuj ==operatora :-) Akceptujemy już tę niedokładność w czasie wykonywania, dlaczego nie również w czasie kompilacji?
Aaron McDaid

3
Zgadzam się z @AaronMcDaid, to nie jest zbyt duży argument. Musisz więc uważać na definicję. Więc co? O ile działa to w przypadku rzeczy, które otrzymujesz ze stałych, jest to już spora poprawa.
einpoklum

1
C ++ 20 pozwala teraz na zmiennoprzecinkowe (inne typy obiektów) jako parametry szablonu innego niż typ. Wciąż C ++ 20 nie określa implementacji typu float. To pokazuje, że einpoklum i Aaron mają rację.
Andreas H.

20

Rzeczywiście, nie można używać literałów typu float jako parametrów szablonu. Patrz sekcja 14.1 („Parametr szablonu inny niż typ powinien mieć jeden z następujących (opcjonalnie kwalifikowanych jako CV) typów…”) normy.

Możesz użyć odwołania do float jako parametru szablonu:

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
Możesz. ale to nie robi tego samego. Nie można użyć odwołania jako stałej czasu kompilacji.

12

Umieść parametr (y) w ich własnej klasie jako constexprs. W rzeczywistości jest to podobne do cechy, ponieważ parametryzuje klasę za pomocą zestawu liczb zmiennoprzecinkowych.

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

a następnie utwórz szablon, przyjmując typ klasy jako parametr

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

a potem użyj go tak ...

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

Dzięki temu kompilator może zagwarantować, że dla każdego wystąpienia szablonu z tym samym pakietem parametrów zostanie utworzone tylko jedno wystąpienie kodu. To omija wszystkie problemy i możesz używać pływaków i podwajać jako constexpr wewnątrz klasy szablonowej.


5

Jeśli możesz mieć stałą wartość domyślną dla typu, możesz utworzyć typ, aby zdefiniować go jako stałą i wyspecjalizować go w razie potrzeby.

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

Jeśli masz C ++ 11, możesz użyć constexpr podczas definiowania wartości domyślnej. W C ++ 14 MyTypeDefault może być zmienną szablonu, która jest nieco czystsza pod względem składniowym.

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

Inne odpowiedzi dają dobre powody, dla których prawdopodobnie nie chcesz parametrów szablonu zmiennoprzecinkowego, ale prawdziwym IMO hamującym transakcje jest to, że równość przy użyciu '==' i równości bitowej to nie to samo:

  1. -0.0 == 0.0, ale 0.0i -0.0nie są równe bitowo

  2. NAN != NAN

Żaden rodzaj równości nie jest dobrym rozwiązaniem dla równości typów: oczywiście, punkt 2. sprawia, że ​​użycie ==równości typów jest nieprawidłowe. Zamiast tego można by użyć równości bitowej, ale wtedy x != ynie oznacza to MyClass<x>i MyClass<y>są to różne typy (przez 2.), co byłoby raczej dziwne.


1

Zawsze możesz to udawać ...

#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


3
A float! = Liczba wymierna. Są to bardzo różne pomysły. Jedna jest obliczana za pomocą mantysy i wykładnika, druga jest, cóż, racjonalna - nie każda wartość reprezentowana przez racjonalną jest reprezentowana przez a float.
Richard J. Ross III

2
@ RichardJ. RossIII A floatjest zdecydowanie liczbą wymierną, ale są liczby, floatktórych nie można przedstawić jako proporcje dwóch ints. Mantysa jest liczbą całkowitą, a wykładnik 2 ^ jest liczbą całkowitą
Caleth

1

Jeśli nie potrzebujesz podwójnego, aby był stałą czasu kompilacji, możesz przekazać go jako wskaźnik:

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

Odniesienie jest prawdopodobnie lepsze, zobacz odpowiedź
@moonshadow

1
Czy to właściwie zmniejsza się w czasie kompilacji?
Ant6n

1

Począwszy od C ++ 20 jest to możliwe .

To również daje odpowiedź na pierwotne pytanie:

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

Ponieważ nikt jeszcze tego nie wdrożył w standardzie. Nie ma żadnego podstawowego powodu.

W C ++ 20 parametrach szablonów innych niż typowe mogą teraz być zmiennoprzecinkowe, a nawet obiekty klas.

Istnieją pewne wymagania dotyczące obiektów klas (muszą być typem literału ) i spełniają inne wymagania, aby wykluczyć patologiczne przypadki, takie jak operator zdefiniowany przez użytkownika == ( Szczegóły ).

Możemy nawet użyć auto

template <auto Val>
struct Test {
};

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

Zauważ, że GCC 9 (i 10) implementuje parametry szablonu inne niż klasowe, ale jeszcze nie dla liczb zmiennoprzecinkowych .


0

Jeśli chcesz reprezentować tylko stałą precyzję, możesz użyć takiej techniki, aby przekonwertować parametr float na wartość int.

Na przykład tablicę ze współczynnikiem wzrostu 1,75 można utworzyć w następujący sposób, zakładając 2 cyfry dokładności (podzielenie przez 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;

Jeśli nie podoba ci się reprezentacja 1,75 jako 175 na liście argumentów szablonu, zawsze możesz owinąć ją jakimś makrem.

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

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

powinno być ...::Factor = _Factor_/100.0;inaczej będzie to dzielenie liczb całkowitych.
alfC
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.