为什么我不能使用浮点值作为模板参数?

当我尝试使用 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,但当我尝试它似乎不编译。

65793 次浏览

当前的 C + + 标准不允许使用 float(即实数)或字符串字面值作为 模板非类型参数。当然可以使用 floatchar *类型作为普通参数。

也许作者使用的编译器不符合当前的标准?

实际上,不能使用浮点数字作为模板参数。

您可以使用对 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 + + 标准指定的。因此,很难确定两个浮点非类型参数是否真正匹配:

template <float f> void foo () ;


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

这些表达式不一定产生相同的“位模式”,因此不可能保证它们使用相同的专门化——如果没有特殊的措辞来涵盖这一点。

简单的答案

该标准不允许浮点数作为 非类型模板参数,这可以在 C + + 11标准的下一节中阅读;

14.3.2/1模板非类型参数[ tem.arg.nontype ]

非类型、非模板模板参数的模板参数 须属下列其中一种:

  • 对于整数或枚举类型的非类型模板参数,转换为 模板参数;

  • 非类型模板参数的名称; 或

  • 一种常量表达式(5.19) ,它指定具有静态存储持续时间和外部或内部连接的对象的地址 或具有外部或内部联系的功能,包括功能 模板和函数模板-id,但不包括非静态类 成员,表示(忽略括号)为 & id 表达式,除非 如果名称引用函数或数组,则 & 可以省略 如果相应的模板参数是 参考文献; 或

  • 计算为空指针值(4.10)的常量表达式; 或

  • 计算为空成员指针值(4.11)的常量表达式; 或

  • 指向5.3.1中描述的成员的指针。


但是. . 但是. . 为什么! ?

这可能是由于浮点计算不能以精确的方式表示。如果允许的话,在做这样的事情时,它可能会导致错误的/奇怪的行为;

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

我们打算调用同一个函数两次,但这可能不是这种情况,因为两个计算的浮点表示不能保证是相同的 没错


如何将浮点值表示为模板参数?

使用 C++11,您可以编写一些相当高级的 常数表达式常数表达式(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;
}

档号: http://code-slim-jim.blogspot.jp/2013/06/c11-no-floats-in-templates-wtf.html

如果只想表示一个固定的精度,那么可以使用这样的技术将 float 参数转换为 int。

例如,假设精度为2位(除以100) ,可以创建增长因子为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 那么你总是可以用宏来包装它。

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


template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,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;
}

如果可以为每个类型设置一个固定的默认值,那么可以创建一个类型,将其定义为常量,并根据需要对其进行专门化。

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

将参数包装在它们自己的类中,作为 Constexprs。实际上,这类似于 trait,因为它使用一组浮点数参数化类。

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

这使编译器可以保证只为具有相同参数包的每个模板实例创建一个代码实例。这解决了所有问题,您可以在模板化类中使用 float 和 double 作为 Constexpr。

其他的答案给出了很好的理由为什么你可能不想要浮点模板参数,但是真正的交易破坏者 IMO 是使用’= =’的相等和位相等是不一样的:

  1. 但是 0.0-0.0不是按位相等的

  2. NAN != NAN

这两种类型的相等都不是类型相等的好的取消者: 当然,第2点使得使用 ==来确定类型相等是无效的。 可以使用按位相等,但是 x != y并不意味着 MyClass<x>MyClass<y>是不同的类型(按2)这很奇怪。

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

注意,GCC9(和10)实现了类非类型模板参数 但还不是为了花车