为什么我不能使用float值作为模板参数?
当我尝试使用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'
我正在阅读Ron Penton的“游戏程序员的数据结构” ,作者通过一个float
,但是当我尝试它时,似乎没有编译。
目前的C ++标准不允许使用float
(即实数)或string文字作为模板非types参数 。 你当然可以使用float
和char *
types作为普通的参数。
也许作者正在使用不符合当前标准的编译器?
简单的答案
该标准不允许将浮点作为非types模板参数 ,这可以在C ++ 11标准的以下部分中了解;
14.3.2 / 1模板非types参数[temp.arg.nontype]
非types的非模板模板参数的模板参数应该是以下之一:
对于整型或枚举types的非types模板参数,模板参数types的转换后的常量expression式(5.19)
非types模板参数的名称; 要么
一个常量expression式(5.19),指定具有静态存储持续时间和外部或内部链接的对象的地址,或者具有外部或内部链接的函数,包括函数模板和函数模板id,但不包括非静态类成员,括号)作为&id-expression,除了如果名称引用函数或数组,则可以省略&,如果相应的模板参数是引用,则省略& 要么
一个常量expression式,其值为空指针值(4.10); 要么
一个常量expression式,其值为空成员指针值(4.11); 要么
一个指向成员的指针,如5.3.1所述。
但..但是..为什么!?
这可能是由于浮点计算不能以精确的方式来表示。 如果被允许,这样做可能会导致错误/怪异的行为;
func<1/3.f> (); func<2/6.f> ();
我们打算两次调用相同的函数,但这可能不是这样,因为两个计算的浮点表示不能保证完全相同 。
我如何将浮点值表示为模板参数?
使用C++11
你可以编写一些相当先进的常量expression式 ( constexpr )来计算浮点值编译时间的分子/分母,然后将这两个参数作为单独的整数parameter passing。
请记住定义某种阈值,以便浮点值彼此接近,得到相同的分子/分母 ,否则就没有意义,因为它会产生前面提到的相同结果,作为不允许将浮点值视为非types的原因模板参数 。
只是为了说明这是一个限制的原因之一(至less在当前的标准中)。
当匹配模板特化时,编译器会匹配模板参数,包括非types参数。
就其性质而言,浮点值不是精确的,它们的实现不是由C ++标准指定的。 因此,很难确定何时两个浮点非types参数真正匹配:
template <float f> void foo () ; void bar () { foo< (1.0/3.0) > (); foo< (7.0/21.0) > (); }
这些expression式不一定产生相同的“位模式”,因此不可能保证他们使用相同的专业化 – 没有特殊的措辞来覆盖这一点。
的确,你不能使用float文字作为模板参数。 参见14.1节 (“非types模板参数应具有下列标准之一(可选cv-qualified)…”) 。
您可以使用对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;
如果您确定每个types的固定默认值,您可以创build一个types来将其定义为一个常量,并根据需要进行专门化。
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可以是一个模板variables,它在语法上有点干净。
//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>; };
将参数作为constexprs包装在自己的类中。 实际上,这与使用一组浮动参数化类的特性相似。
class MyParameters{ public: static constexpr float Kd =1.0f; static constexpr float Ki =1.0f; static constexpr float Kp =1.0f; };
然后创build一个将类types作为参数的模板
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; ... ... }
这样编译器就可以保证只有一个代码实例被创build,每个模板实例化都使用相同的参数包。 这可以解决所有问题,并且可以在模板类中使用花车和双打作为constexpr。
你总是可以伪造它
#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
如果你不需要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; }
如果你只想表示一个固定的精度,那么你可以使用像这样的技术将float参数转换为int。
例如,假定2位精度(除以100),可以如下创build具有1.75的增长因子的数组。
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,那么你总是可以把它包装在一些macros中。
#define FloatToIntPrecision(f,p) (f*(10^p)) template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,2)> // ...