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

yok*_*kks 107 c++ generics floating-point templates

当我尝试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;       
}
Run Code Online (Sandbox Code Playgroud)

错误:

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'
Run Code Online (Sandbox Code Playgroud)

我正在阅读Ron Penton撰写的"游戏程序员的数据结构",作者传递了一个float,但是当我尝试它时似乎没有编译.

Fil*_*efp 128

简单的回答

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

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

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

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

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

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

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

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

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


但是..但是为什么!?

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

func<1/3.f> (); 
func<2/6.f> ();
Run Code Online (Sandbox Code Playgroud)

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


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

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

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

  • C++ 11解决方案是`<ratio>`,由§20.10描述为"编译时合理算术".哪个适合你的例子. (49认同)
  • @iheanyi:标准是否说'12345*12345`是什么?(它*允许`int`模板参数,即使它没有指定signed int的宽度或者该表达式是否为UB.) (4认同)
  • 这实际上并没有给出令人信服的解释。浮点的整个“点”是它确实表示值。您可以自由地将自己拥有的数字视为其他事物的近似值,这样做通常很有用,但是数字本身是准确的。 (3认同)
  • @FilipRoséen-refp:所有浮点数都是准确的。在我所知道的每个目标上,浮点运算都是明确定义的。大多数浮点运算都会产生浮点结果。我很感激委员会不想强迫编译器实现者实现目标的可能的怪异浮点运算,但是我不认为“该运算不同于整数运算”是禁止浮点模板参数的充分理由。在一天结束时,这是一个任意限制。 (3认同)
  • @KeminZhou 我到处都看到这种误解。浮点数不是实数。差远了。事实上,它们是有理数的子集(容易证明)。每个浮点数都严格注入有理数。 (2认同)
  • @tmyklebu 错误。1. 高级语言不一定将浮点值映射到精确的数字概念,甚至不是数学概念。例如,Scheme 语言定义了_inexact_数字的概念,几乎在每个实际实现中都是基于浮点值来实现的。(R6RS甚至定义了_flonums_。)C++没有定义“C++数”的模型,但由于它符合LIA-1(ISO/IEC 10967),因此即使在表示中也存在不精确的空间,除非IEC 60559是紧随其后。 (2认同)
  • 2. 准确性的确定性取决于目标的定义。例如,鉴于 x87 是一个流行的目标,您的假设根本无效:有时 80287 的行为与 80387 以后的行为不同。3. 编译器选项很容易产生大量不兼容的浮点实现,这可能会导致设计上不可预测的行为。例如,GCC 手册规定 -mfpmath=sse+387 的“舍入是不可预测的”,因此 -fexcess- precision=standard 没有任何意义。 (2认同)

小智 34

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

也许作者使用的编译器不遵循现行标准?

  • 请提供标准相关部分的链接或副本 (8认同)
  • @thecoshman标准的相关部分+更多信息可在我(新发布的)答案中找到. (2认同)

Ric*_*den 31

只是提供这是一个限制的原因之一(至少在目前的标准中).

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

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

template <float f> void foo () ;

void bar () {
    foo< (1.0/3.0) > ();
    foo< (7.0/21.0) > ();
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这几乎是禁止完全从语言中浮出水面的论据.或者,至少禁止`==`运算符:-)我们已经在运行时接受这种不准确,为什么不在编译时呢? (14认同)
  • 同意@AaronMcDaid,这不是一个争论.所以你需要在定义中小心.所以呢?只要它适用于你从常数中得到的东西,它就已经有了很大的改进. (3认同)
  • C ++20 现在允许 float(另一种对象类型)作为非类型模板参数。C++20 仍然没有指定 float 实现。这说明einpoklum和Aaron说的都有道理。 (2认同)

moo*_*dow 18

实际上,您不能将浮点文字用作模板参数.参见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;
Run Code Online (Sandbox Code Playgroud)

  • 您可以.但它没有做同样的事情.您不能将引用用作编译时常量. (9认同)

And*_*art 8

将参数作为constexp包装在自己的类中.实际上,这类似于特征,因为它使用一组浮点数对类进行参数化.

class MyParameters{
    public:
        static constexpr float Kd =1.0f;
        static constexpr float Ki =1.0f;
        static constexpr float Kp =1.0f;
};
Run Code Online (Sandbox Code Playgroud)

然后创建一个模板,将类类型作为参数

  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 ...
};
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它......

int main (){
    PidController<float, MyParameters> controller;
    ...
    ...
}
Run Code Online (Sandbox Code Playgroud)

这允许编译器保证仅为具有相同参数包的每个模板实例创建单个代码实例.这解决了所有问题,你可以在模板化的类中使用浮点数和双精度作为constexpr.


And*_* H. 6

从 C++20 开始,这是可能的

这也给出了原始问题的答案:

Why can't I use float value as a template parameter?
Run Code Online (Sandbox Code Playgroud)

因为还没有人在标准中实现它。没有根本的原因。

在 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;
Run Code Online (Sandbox Code Playgroud)

请注意,GCC 9(和 10)实现了类非类型模板参数,但还没有实现浮点数

  • gcc 12.2 现在支持这个:https://godbolt.org/z/MKfe69rvf,但 Clang 仍然不支持 (2认同)

Mat*_*nte 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;
 };
Run Code Online (Sandbox Code Playgroud)

如果您具有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>;
 };
Run Code Online (Sandbox Code Playgroud)