Mengapa saya tidak dapat menggunakan nilai float sebagai parameter template?

120

Ketika saya mencoba untuk menggunakan floatsebagai parameter template, kompilator meminta kode ini, sementara intberfungsi dengan baik.

Apakah karena saya tidak dapat menggunakan floatsebagai parameter template?

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

Kesalahan:

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'

Saya membaca "Struktur Data untuk Pemrogram Game" oleh Ron Penton, penulis melewati a float, tetapi ketika saya mencobanya sepertinya tidak dapat dikompilasi.

yokks
sumber
1
Apakah penulis benar-benar menggunakan floatsebagai parameter template non-tipe ? Dalam bab apakah itu?
K-ballo
1
Menemukannya, ini ada di "Using Values ​​as Template Parameters" ...
K-ballo

Jawaban:

37

Standar C ++ saat ini tidak mengizinkan float(yaitu bilangan real) atau literal string karakter untuk digunakan sebagai parameter non-tipe template . Anda tentu saja dapat menggunakan floatdan char *types sebagai argumen biasa.

Mungkin penulis menggunakan kompilator yang tidak mengikuti standar saat ini?

Filip Roséen - refp
sumber
8
Berikan tautan ke atau salinan bagian yang relevan dari standar
thecoshman
2
@thecoshman bagian yang relevan dari standar + informasi lebih lanjut tersedia di jawaban saya (yang baru diposting).
Filip Roséen - refp
1
Dalam C ++ 11, hampir mungkin untuk menggunakan literal string karakter sebagai parameter non-tipe template. Jika template Anda menggunakan paket karakter template<char ...cs>, maka string literal dapat diubah menjadi paket tersebut pada waktu kompilasi. Berikut ini demo tentang ideone . (Demo adalah C ++ 14, tetapi mudah untuk memindahkannya kembali ke C ++ 11 - std::integer_sequenceadalah satu-satunya kesulitan)
Aaron McDaid
Perhatikan bahwa Anda dapat menggunakan char &*sebagai parameter template jika Anda mendefinisikan literal di tempat lain. Bekerja dengan cukup baik sebagai solusi.
StenSoft
137

JAWABAN SEDERHANA

Standar tidak mengizinkan floating point sebagai non-type template-arguments , yang dapat dibaca di bagian C ++ 11 standar berikut;

14.3.2 / 1 Template argumen non-tipe [temp.arg.nontype]

Template-argumen untuk non-tipe, non-template-parameter template harus salah satu dari:

  • untuk parameter template bukan tipe integral atau tipe enumerasi, ekspresi konstanta yang dikonversi (5.19) dari tipe parameter template;

  • nama dari non-type template-parameter; atau

  • ekspresi konstan (5.19) yang menunjukkan alamat objek dengan durasi penyimpanan statis dan linkage eksternal atau internal atau fungsi dengan linkage eksternal atau internal, termasuk template fungsi dan template-id fungsi tetapi tidak termasuk anggota kelas non-statis, diekspresikan (mengabaikan tanda kurung) sebagai & id-expression, kecuali bahwa & dapat dihilangkan jika nama merujuk ke fungsi atau larik dan harus dihilangkan jika parameter template terkait adalah referensi; atau

  • ekspresi konstan yang mengevaluasi ke nilai pointer nol (4.10); atau

  • ekspresi konstan yang mengevaluasi nilai pointer anggota null (4.11); atau

  • penunjuk ke anggota yang diekspresikan seperti yang dijelaskan pada 5.3.1.


Tapi .. tapi .. KENAPA !?

Mungkin karena fakta bahwa perhitungan floating point tidak dapat disajikan dengan tepat. Jika diizinkan, hal itu dapat / akan menghasilkan perilaku yang keliru / aneh ketika melakukan sesuatu seperti ini;

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

Kami bermaksud memanggil fungsi yang sama dua kali tetapi ini mungkin tidak terjadi karena representasi floating point dari dua penghitungan tidak dijamin akan persis sama.


Bagaimana saya merepresentasikan nilai floating point sebagai argumen template?

Dengan C++11Anda bisa menulis beberapa ekspresi-konstan yang cukup canggih ( constexpr ) yang yang akan menghitung pembilang / penyebut dari waktu kompilasi nilai mengambang dan kemudian meneruskan keduanya sebagai argumen integer terpisah.

Ingatlah untuk menentukan semacam ambang batas sehingga nilai titik mengambang yang dekat satu sama lain menghasilkan pembilang / penyebut yang sama , jika tidak, itu agak tidak berguna karena kemudian akan menghasilkan hasil yang sama yang disebutkan sebelumnya sebagai alasan untuk tidak mengizinkan nilai titik mengambang sebagai bukan tipe argumen template .

Filip Roséen - refp
sumber
56
Solusi C ++ 11 <ratio>, dijelaskan oleh §20.10 sebagai "aritmatika rasional waktu kompilasi". Yang tepat untuk contoh Anda.
Potatoswatter
1
@Potatoswatter afaik tidak ada metode di STL untuk mengubah pelampung menjadi pembilang / penyebut menggunakan <ratio>?
Filip Roséen - refp
3
Ini tidak memberikan penjelasan yang meyakinkan. Seluruh titik floating-point adalah bahwa hal itu mewakili nilai-nilai tepat. Anda bebas memperlakukan angka yang Anda miliki sebagai perkiraan untuk sesuatu yang lain, dan sering kali berguna untuk melakukannya, tetapi angka itu sendiri persis.
tmyklebu
4
@ FilipRoséen-refp: Semua bilangan floating-point tepat. Aritmatika floating-point terdefinisi dengan baik pada setiap target yang saya ketahui. Sebagian besar operasi floating-point menghasilkan hasil floating-point. Saya dapat menghargai panitia yang tidak ingin memaksa pelaksana kompilator untuk mengimplementasikan aritmatika floating-point target yang mungkin aneh, tetapi saya tidak percaya "aritmatika berbeda dari aritmatika integer" adalah alasan yang baik untuk melarang argumen template floating-point. Ini adalah batasan yang sewenang-wenang pada akhirnya.
tmyklebu
5
@iheanyi: Apakah standarnya mengatakan apa 12345 * 12345? (Ini tidak memungkinkan inttemplate parameter meskipun tidak menentukan lebar int ditandatangani atau apakah ekspresi yang UB.)
tmyklebu
34

Hanya untuk memberikan salah satu alasan mengapa ini menjadi batasan (setidaknya dalam standar saat ini).

Saat mencocokkan spesialisasi template, kompilator mencocokkan argumen template, termasuk argumen non-tipe.

Pada dasarnya, nilai floating point tidak tepat dan implementasinya tidak ditentukan oleh standar C ++. Akibatnya, sulit untuk memutuskan kapan dua argumen non-tipe floating point benar-benar cocok:

template <float f> void foo () ;

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

Ekspresi ini tidak selalu menghasilkan "pola bit" yang sama dan oleh karena itu tidak mungkin untuk menjamin bahwa mereka menggunakan spesialisasi yang sama - tanpa kata-kata khusus untuk menutupi ini.

Richard Corden
sumber
16
Ini hampir merupakan argumen untuk melarang float sepenuhnya dari bahasa tersebut. Atau, minimal, blokir ==operator :-) Kami sudah menerima ketidakakuratan ini saat runtime, mengapa tidak pada waktu kompilasi juga?
Aaron McDaid
3
Setuju dengan @AaronMcDaid, ini bukan argumen yang banyak. Jadi Anda perlu berhati-hati dalam mendefinisikannya. Terus? Selama itu berfungsi untuk hal-hal yang Anda dapatkan dari konstanta, itu sudah merupakan peningkatan yang cukup.
einpoklum
1
C ++ 20 sekarang mengizinkan float (tipe objek lain) sebagai parameter template non-tipe. Masih C ++ 20 tidak menentukan implementasi float. Ini menunjukkan bahwa einpoklum dan Harun ada benarnya.
Andreas H.
20

Memang, Anda tidak dapat menggunakan literal float sebagai parameter template. Lihat bagian 14.1 ("Parameter template non-tipe harus memiliki salah satu dari tipe berikut (secara opsional memenuhi kualifikasi cv) ...") dari standar.

Anda dapat menggunakan referensi ke float sebagai parameter template:

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;
bayangan bulan
sumber
11
Kamu bisa. tetapi tidak melakukan hal yang sama. Anda tidak dapat menggunakan referensi sebagai konstanta waktu kompilasi.
12

Bungkus parameter di kelasnya sendiri sebagai konsteks. Secara efektif ini mirip dengan sifat karena parameterisasi kelas dengan satu set float.

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

dan kemudian buat template dengan jenis kelas sebagai parameter

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

dan kemudian gunakan seperti itu ...

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

Hal ini memungkinkan compiler untuk menjamin bahwa hanya satu contoh kode yang dibuat untuk setiap contoh template dengan paket parameter yang sama. Itu mengatasi semua masalah dan Anda bisa menggunakan float dan doubles sebagai constexpr di dalam kelas templated.

Andrew Goedhart
sumber
5

Jika Anda tidak masalah memiliki default tetap per tipe, Anda dapat membuat tipe untuk mendefinisikannya sebagai konstanta dan mengkhususkan sesuai kebutuhan.

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

Jika Anda memiliki C ++ 11, Anda bisa menggunakan constexpr saat menentukan nilai default. Dengan C ++ 14, MyTypeDefault dapat menjadi variabel template yang sedikit lebih bersih secara sintaks.

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

Jawaban lain memberikan alasan bagus mengapa Anda mungkin tidak menginginkan parameter template floating point, tetapi IMO braker real deal adalah bahwa persamaan menggunakan '==' dan persamaan bitwise tidak sama:

  1. -0.0 == 0.0, tetapi 0.0dan -0.0tidak sama dengan bitwise

  2. NAN != NAN

Tidak ada jenis persamaan yang merupakan pembatalan yang baik untuk persamaan jenis: Tentu saja, poin 2. membuat penggunaan ==tidak valid untuk menentukan persamaan jenis. Seseorang dapat menggunakan persamaan bitwise sebagai gantinya, tetapi kemudian x != ytidak menyiratkannya MyClass<x>dan MyClass<y>merupakan tipe yang berbeda (dengan 2.), yang akan agak aneh.

Matthieu
sumber
1

Anda selalu bisa berpura-pura ...

#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
sumber
3
A float! = Bilangan rasional. Keduanya adalah gagasan yang sangat terpisah. Satu dihitung melalui mantissa & eksponen, yang lainnya adalah rasional - tidak setiap nilai yang dapat diwakili oleh rasional dapat diwakili oleh a float.
Richard J. Ross III
2
@ RichardJ.RossIII A floatjelas merupakan bilangan rasional, tetapi ada floats yang tidak dapat direpresentasikan sebagai rasio dua ints. Mantissa adalah Integer, dan eksponen 2 ^ adalah Integer
Caleth
1

Jika Anda tidak memerlukan double untuk menjadi konstanta waktu kompilasi, Anda dapat meneruskannya sebagai pointer:

#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;
}
pengguna3233025
sumber
Referensi mungkin lebih baik, lihat jawaban
@moonshadow
1
Apakah ini benar-benar berkurang pada waktu kompilasi?
Ant6n
1

Dimulai dengan C ++ 20 hal ini dimungkinkan .

Ini juga memberikan jawaban atas pertanyaan awal:

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

Karena belum ada yang menerapkannya dalam standar. Tidak ada alasan mendasar.

Dalam C ++ 20 parameter template non-tipe sekarang dapat berupa float dan bahkan objek kelas.

Ada beberapa persyaratan pada objek kelas (harus berupa tipe literal ) dan memenuhi beberapa persyaratan lain untuk mengecualikan kasus patologis seperti operator yang ditentukan pengguna == ( Detail ).

Kami bahkan bisa menggunakan auto

template <auto Val>
struct Test {
};

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

Perhatikan bahwa GCC 9 (dan 10) mengimplementasikan parameter template non-jenis kelas, tetapi belum untuk float .

Andreas H.
sumber
0

Jika Anda hanya ingin merepresentasikan presisi tetap, Anda dapat menggunakan teknik seperti ini untuk mengubah parameter float menjadi int.

Misalnya larik dengan faktor pertumbuhan 1,75 dapat dibuat sebagai berikut dengan asumsi presisi 2 digit (bagi dengan 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;

Jika Anda tidak menyukai representasi 1,75 sebagai 175 dalam daftar argumen templat maka Anda selalu dapat membungkusnya dalam beberapa makro.

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

template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,2)>
// ...
jurujen
sumber
itu harus ...::Factor = _Factor_/100.0;sebaliknya itu akan menjadi divisi integer.
alfC