当我尝试使用 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 的 "Data Structures for Game Programmers",作者通过了 float
,但是当我尝试它时,它似乎无法编译。
float
作为非类型模板参数吗?这是在哪一章?
简单的答案
该标准不允许浮点作为非类型模板参数,可以在 C++11 标准的下一节中阅读;
14.3.2/1 模板非类型参数 [temp.arg.nontype] 非类型、非模板模板参数的模板参数应为以下之一: 对于整数或枚举的非类型模板参数type,模板参数类型的转换常量表达式(5.19);非类型模板参数的名称;或一个常量表达式 (5.19),它指定具有静态存储持续时间和外部或内部链接的对象或具有外部或内部链接的函数的地址,包括函数模板和函数模板 ID,但不包括非静态类成员,表示为 (忽略括号)作为 & id 表达式,但如果名称引用函数或数组,则 & 可以省略,如果相应的模板参数是引用,则应省略;或计算为空指针值的常量表达式(4.10);或计算结果为空成员指针值的常量表达式(4.11);或指向成员的指针,如 5.3.1 所述。
但是..但是..为什么!?
这可能是由于浮点计算不能以精确的方式表示。如果允许这样做,则可能/将导致错误/奇怪的行为;
func<1/3.f> ();
func<2/6.f> ();
我们打算调用同一个函数两次,但情况可能并非如此,因为不能保证两次计算的浮点表示完全相同。
我如何将浮点值表示为模板参数?
使用 C++11
,您可以编写一些非常高级的 constant-expressions (constexpr) 来计算浮点值编译时间的分子/分母,然后将这两个单独传递整数参数。
请记住定义某种阈值,以便彼此接近的浮点值产生相同的分子/分母,否则它会产生相同的结果,因为它会产生前面提到的相同结果,作为不允许浮点值作为非类型的原因模板参数。
当前的 C++ 标准不允许将 float
(即实数)或字符串文字用作 模板非类型参数。您当然可以使用 float
和 char *
类型作为普通参数。
也许作者正在使用不遵循当前标准的编译器?
template<char ...cs>
,则可以在编译时将字符串文字转换为这样的包。这是一个demo on ideone。 (演示是 C++14,但很容易将其移植回 C++11 - std::integer_sequence
是唯一的困难)
char &*
作为模板参数。作为一种解决方法效果很好。
只是为了提供这是一个限制的原因之一(至少在当前标准中)。
当匹配模板特化时,编译器匹配模板参数,包括非类型参数。
就其本质而言,浮点值并不精确,并且 C++ 标准未指定其实现。因此,很难确定两个浮点非类型参数何时真正匹配:
template <float f> void foo () ;
void bar () {
foo< (1.0/3.0) > ();
foo< (7.0/21.0) > ();
}
这些表达式不一定会产生相同的“位模式”,因此无法保证它们使用相同的专业化 - 没有特殊的措辞来涵盖这一点。
==
运算符 :-) 我们已经在运行时接受了这种不准确性,为什么不在编译时也接受呢?
实际上,您不能使用浮点文字作为模板参数。请参阅标准的 section 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;
将参数包装在自己的类中作为 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;
...
...
}
这允许编译器保证为每个具有相同参数包的模板实例化只创建一个代码实例。这解决了所有问题,您可以在模板类中使用浮点数和双精度数作为 constexpr。
typename TUNING_PARAMS
可以在模板参数。这对于确保只传递定义所需参数的正确类型可能很有用。也许也可以传递引用,在这种情况下也可以传递子类..?
从 C++20 开始,这是 possible。
这也给出了原始问题的答案:
Why can't I use float value as a template parameter?
因为还没有人在标准中实现它。没有根本原因。
在 C++20 中,非类型模板参数现在可以是浮点数甚至是类对象。
对类对象有一些要求(它们必须是 literal type)并满足一些其他要求以排除异常情况,例如用户定义的运算符 == (Details)。
我们甚至可以使用 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)实现类非类型模板参数 but not for floats yet。
如果您可以为每种类型设置一个固定的默认值,您可以创建一个类型以将其定义为常量并根据需要对其进行专门化。
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
float
!= 有理数。两者是非常不同的想法。一个是通过尾数和一个指数,另一个是有理数 - 并非每个由有理数表示的值都可以由 float
表示。
float
绝对是一个有理数,但有些 float
不能表示为两个 int
的比率。尾数是整数,2^exponent 是整数
其他答案给出了您可能不想要浮点模板参数的充分理由,但真正的交易破坏者 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。
例如,假设 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)>
// ...
...::Factor = _Factor_/100.0;
否则它将是整数除法。
不定期副业成功案例分享
<ratio>
,第 20.10 节将其描述为“编译时有理算术”。这切到了你的例子。12345 * 12345
是什么? (它确实允许int
模板参数,即使它没有指定带符号 int 的宽度或该表达式是否为 UB。)