ChatGPT解决这个技术问题 Extra ChatGPT

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

当我尝试使用 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
Community

简单的答案

该标准不允许浮点作为非类型模板参数,可以在 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++11 解决方案是 <ratio>,第 20.10 节将其描述为“编译时有理算术”。这切到了你的例子。
这并没有真正给出令人信服的解释。浮点的全部意义在于它确实准确地表示了值。您可以自由地将您拥有的数字视为其他事物的近似值,这样做通常很有用,但数字本身是准确的。
@FilipRoséen-refp:所有浮点数都是精确的。浮点运算在我知道的每个目标上都有很好的定义。大多数浮点运算产生浮点结果。我可以理解委员会不想强迫编译器实现者实现目标的可能奇怪的浮点算术,但我不认为“算术不同于整数算术”是禁止浮点模板参数的好理由。这是一天结束时的任意限制。
@iheanyi:标准是否说明 12345 * 12345 是什么? (它确实允许 int 模板参数,即使它没有指定带符号 int 的宽度或该表达式是否为 UB。)
@KeminZhou 我到处都能看到这种误解。浮点数不是实数。差远了。事实上,它们是有理数的一个子集(很容易证明)。每个浮点数都严格注入有理数。
F
Filip Roséen - refp

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

也许作者正在使用不遵循当前标准的编译器?


请提供标准中相关部分的链接或副本
@thecoshman 标准的相关部分+更多信息可在我的(新发布的)答案中找到。
在 C++11 中,几乎可以使用字符串文字作为模板非类型参数。如果您的模板采用字符包 template<char ...cs>,则可以在编译时将字符串文字转换为这样的包。这是一个demo on ideone。 (演示是 C++14,但很容易将其移植回 C++11 - std::integer_sequence 是唯一的困难)
请注意,如果您在其他地方定义文字,则可以使用 char &* 作为模板参数。作为一种解决方法效果很好。
R
Richard Corden

只是为了提供这是一个限制的原因之一(至少在当前标准中)。

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

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

template <float f> void foo () ;

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

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


这几乎是完全禁止语言中的浮点数的论点。或者,至少禁止 == 运算符 :-) 我们已经在运行时接受了这种不准确性,为什么不在编译时也接受呢?
同意@AaronMcDaid,这没什么好争论的。所以你需要在定义中小心。所以呢?只要它适用于从常量中获得的东西,它就已经是相当大的改进了。
C ++20 现在允许将浮点(其他对象类型)作为非类型模板参数。仍然 C++20 没有指定浮点实现。这表明 einpoklum 和 Aaron 说得有道理。
m
moonshadow

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

你可以。但它不做同样的事情。您不能将引用用作编译时常量。
A
Andrew Goedhart

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


如果 C++20 是一个选项,则可以通过将结构或类实例按值传递到模板(如 stackoverflow.com/a/60244416/6177253 中所示)在此处使意图更加清晰,因此 typename TUNING_PARAMS 可以在模板参数。这对于确保只传递定义所需参数的正确类型可能很有用。也许也可以传递引用,在这种情况下也可以传递子类..?
A
Andreas H.

从 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


但是,这里指定了一个解决方法:open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1714r1.html(在第一章的末尾)
M
Matthew Fioravante

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

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

A
Ashley Smart

你总是可以假装...

#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 表示。
@RichardJ.RossIII float 绝对是一个有理数,但有些 float 不能表示为两个 int 的比率。尾数是整数,2^exponent 是整数
M
Matthieu

其他答案给出了您可能不想要浮点模板参数的充分理由,但真正的交易破坏者 IMO 是使用 '==' 的相等性和按位相等性是不一样的:

-0.0 == 0.0,但 0.0 和 -0.0 按位不相等 NAN != NAN

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


已经有枚举器(具有相同的基础整数值)能够作为模板参数,并且它们在类型等价的意义上并不相同,所以你的第一点不是问题。
u
user3233025

如果您不需要 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 的answer
这实际上在编译时正确减少了吗?
j
jurujen

如果您只想表示一个固定的精度,那么您可以使用这样的技术将浮点参数转换为 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; 否则它将是整数除法。