Почему я не могу использовать значение с плавающей запятой в качестве параметра шаблона? - PullRequest
102 голосов
/ 02 февраля 2010

Когда я пытаюсь использовать 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, но, когда я пытаюсь это сделать, он не компилируется.

Ответы [ 10 ]

121 голосов
/ 17 июля 2012

ПРОСТОЙ ОТВЕТ

Стандарт не допускает использование чисел с плавающей запятой в качестве нетипизированных аргументов шаблона , о которых можно прочитать в следующем разделе стандарта 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 ), которые будут вычислять числитель / знаменатель времени компиляции с плавающим значением, а затем передавать эти два как отдельные целочисленные аргументы.

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

33 голосов
/ 02 февраля 2010

Текущий стандарт C ++ не позволяет использовать float (то есть действительное число) или символьные строковые литералы в качестве нетиповых параметров шаблона . Конечно, вы можете использовать типы float и char * в качестве обычных аргументов.

Возможно, автор использует компилятор, который не соответствует текущему стандарту?

30 голосов
/ 02 февраля 2010

Просто чтобы указать одну из причин, по которой это ограничение (по крайней мере, в текущем стандарте).

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

По своей природе значения с плавающей точкой не являются точными, и их реализация не определена стандартом C ++. В результате трудно определить, действительно ли совпадают два аргумента без типа с плавающей точкой:

template <float f> void foo () ;

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

Эти выражения не обязательно производят одну и ту же "битовую комбинацию", и поэтому было бы невозможно гарантировать, что они использовали одну и ту же специализацию - без специальной формулировки, чтобы покрыть это.

18 голосов
/ 02 февраля 2010

Действительно, вы не можете использовать литералы с плавающей точкой в ​​качестве параметров шаблона. См. раздел 14.1 («Шаблонный параметр не-типа должен иметь один из следующих (необязательно квалифицированных cv) типов ...») стандарта.

Вы можете использовать ссылку на число с плавающей точкой в ​​качестве параметра шаблона:

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;
6 голосов
/ 16 августа 2016

Обернуть параметр (ы) в своем собственном классе как constexprs. Фактически это похоже на черту, поскольку параметризует класс с помощью набора чисел с плавающей запятой.

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 и double как constexpr внутри шаблонного класса.

5 голосов
/ 30 сентября 2015

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

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>;
 };
1 голос
/ 07 мая 2015

Если вам не нужно, чтобы double был константой времени компиляции, вы можете передать его как указатель:

#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;
}
1 голос
/ 08 июня 2013

Вы всегда можете подделать это ...

#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

0 голосов
/ 10 мая 2019

Другие ответы дают веские причины, по которым вам, вероятно, не нужны параметры шаблона с плавающей запятой, но реальное препятствие IMO заключается в том, что равенство с использованием '==' и битовое равенство не совпадают:

  1. -0.0 == 0.0, но 0.0 и -0.0 не поразрядно равны

  2. NAN != NAN

Ни один из видов равенства не является хорошим кандидатом для равенства типов: Конечно, пункт 2. делает использование == недопустимым для определения равенства типов. Вместо этого можно использовать битовое равенство, но тогда x != y не означает, что MyClass<x> и MyClass<y> - это разные типы (на 2.), что было бы довольно странно.

0 голосов
/ 02 июня 2014

Если вы хотите представлять только фиксированную точность, вы можете использовать такую ​​технику, чтобы конвертировать параметр с плавающей точкой в ​​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)>
// ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...