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

时间:2010-02-02 09:50:24

标签: c++ templates generics floating-point

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

10 个答案:

答案 0 :(得分:120)

简单的答案

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

  

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

     

非类型非模板模板参数的模板参数   应该是以下之一:

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

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

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

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

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

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

  •   

但是..但是为什么!?

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

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

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


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

使用C++11,您可以编写一些非常高级的常量表达式 constexpr ),它们将计算浮点值编译时的分子/分母,然后将这两个作为单独的整数参数传递。

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

答案 1 :(得分:33)

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

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

答案 2 :(得分:31)

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

匹配模板特化时,编译器会匹配模板参数,包括非类型参数。

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

template <float f> void foo () ;

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

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

答案 3 :(得分:18)

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

答案 4 :(得分: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>;
 };

答案 5 :(得分:2)

从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

答案 6 :(得分: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

答案 7 :(得分: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;
}

答案 8 :(得分: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)>
// ...

答案 9 :(得分:0)

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

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

  2. NAN != NAN

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