为什么不能将float值用作模板参数?


120

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


1
作者是否真的将其float用作非类型模板参数?那是哪一章?
K-ballo 2013年

1
找到它,它位于“使用值作为模板参数” ...
K-ballo

Answers:


37

当前的C ++标准不允许float(即实数)或字符串文字用作模板非类型参数。您当然可以将floatchar *类型用作常规参数。

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


8
请提供该标准相关部分的链接或副本
thecoshman

2
@thecoshman标准的相关部分+更多信息,请参阅我的(新发布的)答案。
FilipRoséen-refp 2012年

1
在C ++ 11中,几乎可以将字符串文字用作模板非类型参数。如果您的模板带有一个字符包template<char ...cs>,则可以在编译时将字符串文字转换为这种字符包。这是有关ideone演示。(演示是C ++ 14,但很容易将其移植回C ++ 11-这std::integer_sequence是唯一的困难)
Aaron McDaid

请注意,char &*如果您在其他地方定义文字,则可以将其用作模板参数。可以很好地解决。
StenSoft

137

简单的答案

该标准不允许将浮点作为非类型模板参数,可以在C ++ 11标准的以下部分中进行阅读;

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

非类型,非模板模板参数的模板参数应为以下之一:

  • 对于整数或枚举类型的非类型模板参数,使用该模板参数类型的转换后的常数表达式(5.19);

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

  • 一个常数表达式(5.19),它指定具有静态存储持续时间和外部或内部链接的对象的地址,或者具有外部或内部链接的函数的地址,包括函数模板和函数模板ID,但不包括非静态类成员,表示为(忽略括号)作为&id-expression,但如果名称引用函数或数组,则可以省略&;如果相应的模板参数为引用,则应省略;要么

  • 一个常量表达式,其值为空指针值(4.10);要么

  • 一个常量表达式,其值为空成员指针值(4.11);要么

  • 指向成员的指针,如5.3.1中所述。


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

可能是由于无法以精确的方式表示浮点计算。如果允许的话,这样做可能会导致错误/怪异的行为。

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

我们本来打算两次调用相同的函数,但事实并非如此,因为不能保证两个计算的浮点表示形式完全相同


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

随着C++11你可以写一些非常先进的常量表达式constexpr),将计算出的浮动值,编译时的分子/分母,然后通过这两个作为单独的整数参数。

记住要定义某种阈值,以使彼此接近的浮点值产生相同的分子/分母,否则它是无意义的,因为它随后将产生前面提到的相同结果,作为不允许浮点值作为 非类型的原因模板参数


56
<ratio>§20.10 将C ++ 11解决方案描述为“编译时有理算术”。这恰好适合您的示例。
Potatoswatter

1
@Potatoswatter afaik STL中没有任何方法使用<ratio>?将浮点数转换为分子/分母?
FilipRoséen-refp 2012年

3
这实际上并没有给出令人信服的解释。浮点的全部意义在于它确实表示值。您可以自由地将自己拥有的数字视为其他事物的近似值,这样做通常很有用,但是数字本身是准确的。
tmyklebu 2015年

4
@FilipRoséen-refp:所有浮点数都是准确的。在我所知道的每个目标上,浮点运算都是明确定义的。大多数浮点运算都会产生浮点结果。我很感激委员会不想强迫编译器实现者实现目标的可能的怪异浮点算法,但是我不认为“该算法与整数算法不同”是禁止浮点模板参数的充分理由。在一天结束时,这是一个任意的限制。
tmyklebu 2015年

5
@iheanyi:标准是否说明了什么12345 * 12345?(即使它未指定有符号的int的宽度或该表达式是否为UB,它允许使用int模板参数。)
tmyklebu 2016年

34

只是提供限制的原因之一(至少在当前标准中如此)。

匹配模板专业化时,编译器将匹配模板参数,包括非类型参数。

就其本质而言,浮点值并不精确,并且C ++标准未指定其实现。结果,很难确定两个浮点非类型参数何时真正匹配:

template <float f> void foo () ;

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

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


16
这几乎是一个完全禁止语言浮动的论点。或者,至少禁止==操作员:-)我们已经在运行时接受了这种不准确性,为什么在编译时也不会呢?
亚伦·麦克戴德

3
同意@AaronMcDaid,这并不是一个太多的争论。因此,在定义时需要小心。所以呢?只要它适用于您从常量中获得的东西,它就已经是相当大的改进。
einpoklum's

1
现在,C ++ 20允许将float(其他对象类型)作为非类型模板参数。C ++ 20仍未指定float实现。这表明einpoklum和Aaron有观点。
Andreas H.

20

实际上,您不能将浮点文字用作模板参数。参见标准的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;

11
您可以。但它不会做同样的事情。您不能将引用用作编译时常量。

12

将参数包装在自己的类中作为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。


5

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

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

2

其他答案给出了为什么您可能不希望使用浮点模板参数的充分理由,但是真正的限制因素IMO是使用'=='的相等性和按位相等性是不同的:

  1. -0.0 == 0.0,但0.0-0.0按位不相等

  2. NAN != NAN

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


1

您可以随时伪造.​​..

#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


3
一个float!=有理数。两者是非常独立的想法。一个是通过尾数和指数计算的,另一个是有理数-并非每个有理数表示的值都可以由a表示float
理查德·罗斯三世

2
@ RichardJ.RossIII A float绝对是一个有理数,但其中的floats不能表示为2 int的比率。尾数是整数,而2的指数是整数
Caleth

1

如果不需要将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;
}

参考可能更好,请参阅@moonshadow的答案
einpoklum 2016年

1
这实际上在编译时会适当减少吗?
Ant6n

1

从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


0

如果只想表示固定的精度,则可以使用类似的技术将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)>
// ...

否则应为...::Factor = _Factor_/100.0;整数除法。
alfC 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.