Чому я не можу використовувати значення float як параметр шаблону?


120

Коли я намагаюся використовувати floatяк параметр шаблону, компілятор кричить за цим кодом, при цьому intпрацює добре.

Це тому, що я не можу використовувати floatяк параметр шаблону?

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

Помилка:

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'

Я читаю "Структури даних для програмістів ігор" Рона Пентона, автор передає "А" float, але коли я спробую це, здається, не збирається.


1
Чи має автор дійсно використовувати в floatякості параметра шаблона НЕ типу ? У якій главі це?
К-бал

1
Виявив це, це на "Використання значень як параметрів шаблону" ...
K-ballo

Відповіді:


37

Поточний стандарт C ++ не дозволяє float(тобто реальне число) або літеральні рядки символів використовувати як параметри нетипового шаблону . Звичайно, ви можете використовувати floatі char *типи як звичайні аргументи.

Можливо, автор використовує компілятор, який не відповідає чинному стандарту?


8
Будь ласка, надайте посилання на або копію відповідного розділу зі стандарту
thecoshman

2
@thecoshman відповідний розділ стандарту + додаткова інформація доступна у моїй (щойно опублікованій) відповіді.
Філіп Розен - реп.

1
У C ++ 11, майже, можливо, використовувати символьний рядковий рядок як параметр нетипового шаблону. Якщо ваш шаблон займає пакет символів template<char ...cs>, то літеральний рядок може бути перетворений у такий пакет під час компіляції. Ось демонстрація на ideone . (Демонстрація C ++ 14, але її легко перенести на C ++ 11 - std::integer_sequenceєдина складність)
Аарон Макдейд

Зауважте, що ви можете використовувати char &*як параметр шаблону, якщо ви визначаєте букваль де-небудь ще. Працює досить добре як вирішення проблеми.
StenSoft

137

ПРОСТИЙ ВІДПОВІДЬ

Стандарт не дозволяє плаваючі точки як нетипові аргументи шаблону , про які можна прочитати в наступному розділі стандарту C ++ 11;

14.3.2 / 1 Аргументи нетипового шаблону [temp.arg.nontype]

Аргумент шаблону для нетипового, не шаблонного параметра шаблону повинен бути одним із:

  • для нетипового шаблону-параметра інтегрального чи перелічувального типу перетворений постійний вираз (5.19) типу шаблону-параметра;

  • назва шаблону-параметра нетипового типу; або

  • постійний вираз (5.19), який позначає адресу об'єкта зі статичною тривалістю зберігання та зовнішнім чи внутрішнім зв’язком або функцією із зовнішнім чи внутрішнім зв’язком, включаючи шаблони функцій та шаблони функцій, але виключаючи нестатичні члени класу, виражається (ігноруючи круглі дужки) як & id-вираз, за ​​винятком того, що & може бути опущено, якщо ім'я посилається на функцію чи масив, і опускати, якщо відповідний шаблон-параметр є посиланням; або

  • постійний вираз, що оцінює нульове значення вказівника (4.10); або

  • постійний вираз, який оцінює нульове значення вказівника члена (4.11); або

  • вказівник на члена, виражений, як описано в 5.3.1.


Але .. але .. ЧОМУ !?

Можливо, це пов'язано з тим, що обчислення з плаваючою комою не можуть бути представлені точно. Якщо це було дозволено, це могло / призвело б до помилкової / дивної поведінки, роблячи щось таке;

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

Ми мали намір викликати одну і ту ж функцію двічі, але це може бути не так, оскільки представлення двох обчислень з плаваючою комою не гарантовано буде абсолютно однаковим.


Як я можу представляти значення з плаваючою комою як аргументи шаблону?

З C++11вами можна написати деякі досить вдосконалені постійні вирази ( constexpr ), які обчислили б чисельник / знаменник часу компіляції плаваючого значення, а потім передадуть ці два як окремі цілі аргументи.

Не забудьте визначити якийсь поріг, щоб значення з плаваючою точкою близько один до одного давали той самий чисельник / знаменник , інакше це свого роду безглуздо, оскільки потім вийде той самий результат, що згадувався раніше, як причина не допускати значення плаваючої точки як нетипові аргументи шаблону .


56
Рішення C ++ 11 <ratio>описується пунктом 20.10 як "Раціональна арифметика у часі складання". Що вирішує ваш приклад.
Potatoswatter

1
@Potatoswatter afaik Не існує жодного методу в STL для перетворення поплавця в чисельник / знаменник за допомогою <ratio>?
Філіп Розен - реп.

3
Це насправді не дає переконливого пояснення. Вся суть з плаваючою комою полягає в тому, що вона дійсно представляє значення. Ви можете ставитися до чисел, які ви маєте, як наближення до чогось іншого, і це часто корисно робити, але самі цифри є точними.
tmyklebu

4
@ FilipRoséen-refp: Усі числа з плаваючою комою є точними. Арифметика з плаваючою комою чітко визначена на кожній мені цілі. Більшість операцій з плаваючою комою дають результати з плаваючою комою. Я можу оцінити, що комітет не бажає змушувати розробників компілятора реалізовувати цілком можливо химерну арифметику з плаваючою комою, але я не вірю, що "арифметика відрізняється від арифметики з цілочисельною лінією" є вагомою причиною заборонити аргументи шаблону з плаваючою комою. Це довільне обмеження наприкінці дня.
tmyklebu

5
@iheanyi: Чи говорить стандарт, що 12345 * 12345є? (Це дійсно дозволяють intпараметри шаблону , навіть якщо він не визначає ширину підписаних межд або ж цей вислів UB.)
tmyklebu

34

Просто надати одну з причин, чому це обмеження (принаймні в чинному стандарті).

При узгодженні спеціалізацій шаблонів компілятор відповідає аргументам шаблону, включаючи нетипові аргументи.

За своєю суттю значення плаваючої крапки не є точними, а їх реалізація не визначена стандартом C ++. Як наслідок, важко визначити, коли два аргументи без типу з плаваючою точкою дійсно відповідають:

template <float f> void foo () ;

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

Ці вирази не обов'язково створюють один і той же «бітовий шаблон», і тому було б неможливо гарантувати, що вони використовували ту саму спеціалізацію - без особливих формулювань для цього.


16
Це майже аргумент, щоб заборонити floats повністю з мови. Або, як мінімум, заборонити ==оператору :-) Ми вже приймаємо цю неточність під час виконання, чому ж не під час компіляції?
Аарон Макдейд

3
Погодьтеся з @AaronMcDaid, це не є великим аргументом. Тому потрібно бути обережними у визначенні. І що? Поки це працює на речі, які ви отримуєте від констант, це вже зовсім вдосконалення.
einpoklum

1
Тепер C ++ 20 дозволяє використовувати float (інші типи об'єктів) як параметри шаблону нетипового типу. Ще C ++ 20 не визначає реалізацію float. Це показує, що ейнпоклум та Аарон мають крапку.
Андреас Х.

20

Дійсно, не можна використовувати float літерали як параметри шаблону. Дивіться розділ 14.1 ("Нетиповий параметр шаблону повинен мати один із наступних (необов'язково cv) типів ..." стандарту.

Ви можете використовувати посилання на float як параметр шаблону:

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
Ти можеш. але це не робить те ж саме. Ви не можете використовувати посилання як константа часу компіляції.

12

Оберніть параметри (и) у власному класі як конспектори. Ефективно це схоже на ознаку, оскільки параметризує клас із набором плавців.

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

а потім створити шаблон, приймаючи тип класу як параметр

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

а потім використовуйте його так ...

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

Це дозволяє компілятору гарантувати, що для кожного екземпляра шаблону з тим самим пакетом параметрів створюється лише один екземпляр коду. Це дозволяє вирішити всі проблеми, і ви можете використовувати floats та doubles як constexpr всередині шаблонного класу.


5

Якщо у вас все нормально, щоб мати фіксований за замовчуванням для кожного типу, ви можете створити тип, щоб визначити його як постійний і спеціалізувати його за потребою.

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

Якщо у вас є C ++ 11, ви можете використовувати constexpr, визначаючи значення за замовчуванням. З C ++ 14 MyTypeDefault може бути змінною шаблону, яка є синтаксично чистішою.

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

Інші відповіді дають вагомі причини, чому ви, мабуть, не хочете параметрів шаблону з плаваючою точкою, але справжній IMO-гайдач справжньої угоди полягає в тому, що рівність із використанням '==' та бітової рівності не однакові:

  1. -0.0 == 0.0, але 0.0і -0.0не є порозрядно рівними

  2. NAN != NAN

Жоден вид рівності не є хорошим кандидатом для рівності типів: Звичайно, пункт 2. робить використання ==недійсним для визначення рівності типу. Можна використовувати побітовое рівність замість цього, але x != yне означає , що MyClass<x>і MyClass<y>різні типи (по 2), яке було б досить дивно.


1

Ви завжди можете підробити це ...

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

Посилання: http://code-slim-jim.blogspot.jp/2013/06/c11-no-floats-in-templates-wtf.html


3
A float! = Раціональне число. Два дуже ідеї. Одне обчислюється через мантісу та показник, а інше - раціональне - не кожне значення, що може бути представлене раціональним, представлене a float.
Річард Дж. Росс III

2
@ RichardJ.RossIII A float, безумовно, раціональне число, але є floats, які не є репрезентативними як відношення двох ints. Mantissa - ціле число, а показник 2 ^ - ціле число
Caleth

1

Якщо вам не потрібен подвійний, щоб бути констатою часу компіляції, ви можете передати його як покажчик:

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

Посилання, ймовірно, краще, див. Відповідь
@moonshadow

1
Це насправді правильно скорочується під час компіляції?
Ant6n

1

Починаючи з C ++ 20 це можливо .

Це також дає відповідь на початкове запитання:

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

Тому що ще ніхто не впровадив його у стандарт. Принципової причини немає.

У C ++ 20 параметрами шаблону нетипового типу тепер можуть бути поплавці та навіть об’єкти класу.

Існують деякі вимоги до об'єктів класу (вони повинні бути буквальним типом ) та виконувати деякі інші вимоги для виключення патологічних випадків, таких як визначений користувачем оператор == ( Деталі ).

Ми навіть можемо використовувати auto

template <auto Val>
struct Test {
};

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

Зауважте, що GCC 9 (і 10) реалізує параметри шаблону нетипового шаблону класу, але ще не для плавців .


0

Якщо ви хочете представляти лише фіксовану точність, тоді ви можете скористатися такою технікою, щоб перетворити параметр float в int.

Наприклад, масив з коефіцієнтом росту 1,75 можна створити наступним чином, припускаючи 2 цифри точності (ділити на 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;

Якщо вам не подобається представлення 1,75 як 175 у списку аргументів шаблону, то ви завжди можете зафіксувати його в якомусь макросі.

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

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

в ...::Factor = _Factor_/100.0;іншому випадку це буде ціле ділення.
alfC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.