Почему я не могу использовать значение с плавающей точкой в качестве параметра шаблона?
Когда я пытаюсь использовать 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
, но когда я пытаюсь это не похоже компилируется.
9 ответов
Текущий стандарт C++ не позволяет float
(т.е. действительное число) или строковые литералы символов, которые будут использоваться в качестве нетиповых параметров шаблона. Вы можете, конечно, использовать float
а также char *
Типы как обычные аргументы.
Возможно, автор использует компилятор, который не соответствует текущему стандарту?
ПРОСТОЙ ОТВЕТ
Стандарт не допускает использование плавающих точек как нетиповых аргументов шаблона, о чем можно прочитать в следующем разделе стандарта 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), которые вычисляли бы числитель / знаменатель времени компиляции с плавающим значением, а затем передавали эти два в виде отдельных целочисленных аргументов.
Не забудьте определить некоторый порог, чтобы значения с плавающей запятой, близкие друг к другу, давали один и тот же числитель / знаменатель, в противном случае это было бы бессмысленно, поскольку в результате получался тот же результат, который упоминался ранее в качестве причины, чтобы не допускать использования значений с плавающей запятой как нетиповых аргументы шаблона.
Просто чтобы представить одну из причин, почему это ограничение (по крайней мере, в текущем стандарте).
При сопоставлении специализаций шаблона компилятор сопоставляет аргументы шаблона, включая не типовые аргументы.
По своей природе значения с плавающей точкой не являются точными, и их реализация не определена стандартом C++. В результате трудно определить, действительно ли совпадают два не типовых аргумента с плавающей точкой:
template <float f> void foo () ;
void bar () {
foo< (1.0/3.0) > ();
foo< (7.0/21.0) > ();
}
Эти выражения не обязательно производят одну и ту же "битовую комбинацию", и поэтому было бы невозможно гарантировать, что они использовали одну и ту же специализацию - без специальной формулировки, чтобы покрыть это.
Действительно, вы не можете использовать литералы с плавающей точкой в качестве параметров шаблона. См. Раздел 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;
Начиная с 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) реализует параметры шаблона, не относящиеся к типу, но еще не для float.
Оберните параметр (ы) в их собственный класс как 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 внутри шаблонного класса.
Если вы можете использовать фиксированное значение по умолчанию для каждого типа, вы можете создать тип, чтобы определить его как константу и специализировать его по мере необходимости.
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>;
};
Вы всегда можете подделать это...
#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
Другие ответы дают веские причины, почему вам, вероятно, не нужны параметры шаблона с плавающей запятой, но реальный тормоз IMO заключается в том, что равенство с использованием '==' и побитовое равенство не одно и то же:
-0.0 == 0.0
, но0.0
а также-0.0
не побитовое равенствоNAN != NAN
Ни один из видов равенства не является хорошим признаком равенства типов: конечно, пункт 2. делает использование ==
недопустим для определения равенства типов. Вместо этого можно было бы использовать побитовое равенство, но тогдаx != y
не означает, что MyClass<x>
а также MyClass<y>
бывают разных типов (на 2.), что было бы довольно странно.
Если вам не нужно, чтобы 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;
}
Если вы хотите представлять только фиксированную точность, вы можете использовать такую технику для преобразования параметра с плавающей точкой в 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)>
// ...