为啥不允许“内联”静态常量,除了整数?

Posted

技术标签:

【中文标题】为啥不允许“内联”静态常量,除了整数?【英文标题】:Why are "inlined" static consts not allowed, except ints?为什么不允许“内联”静态常量,除了整数? 【发布时间】:2009-12-15 12:47:52 【问题描述】:

可能重复Why can't I have a non-integral static const member in a class?

struct Example

    static const int One = 1000; // Legal
    static const short Two = 2000; // Illegal
    static const float Three = 2000.0f; // Illegal
    static const double Four = 3000.0; // Illegal
    static const string Five = "Hello"; // Illegal
;

#2、#3、#4 和 #5 有什么不合法的原因吗?

我想我知道#5 的原因:编译器需要一个“真正的”字符串对象(因为它不是内置类型)并且不能盲目地将Five 替换为"Hello",就好像它是#define Five "Hello"。但如果是这种情况,编译器不能在 .obj 文件中留下提示并告诉链接器在某处自动创建 string Five 的一个实例吗?

对于#3 和#4,尤其是#2(哈哈!)...我真的看不出任何可能的原因!浮点数和双精度数是内置类型,就像 int 一样!而 short 只是一个(可能)更短的整数。


编辑:我使用 Visual Studio 2008 编译它。我认为在这种情况下所有编译器的行为都相同,但显然 g++ 编译得很好(#5除外)。 VS 为该 sn-ps 给出的错误是:

错误 C2864:“Example::Two”:只能在类中初始化静态 const 整数数据成员 错误 C2864:'Example::Three':只能在类中初始化静态 const 整数数据成员 错误 C2864:'Example::Four':只能在类中初始化静态 const 整数数据成员 错误 C2864:'Example::Five':只能在类中初始化静态 const 整数数据成员

【问题讨论】:

你在不同的编译器上试过了吗?如果有,是哪个?错误信息在哪里? 你用的是什么编译器?用 G++ 编译对我来说很好(字符串除外)。 VS2008(编辑主要问题) @schnaader:你应该用过 -pedantic。 复制:***.com/questions/370283/… 【参考方案1】:

int 和 short 是合法的,如果你的编译器不允许它们,那么你的编译器就失败了:

9.4.2/4: ... 如果静态数据成员是 const integer 或 const 枚举类型,它的声明在 类定义可以指定一个 constant-initializer 应该是一个整数常量表达式。

我相信浮点数和双精度数在 C++ 标准中没有像整数类型那样被特别视为常量的原因是 C++ 标准对浮点数和双精度数的算术运算可能很微妙在编译机器上,它们与在执行代码的机器上不同。编译器要计算像 (a + b) 这样的常量表达式,它需要得到与运行时相同的答案。

这对于整数来说不是什么大问题 - 如果整数运算不同,您可以相对便宜地模拟整数运算。但是编译器在目标设备上模拟浮点硬件可能非常困难。如果有不同版本的芯片并且编译器不知道代码将在哪个版本上运行,这甚至可能是不可能的。这甚至在你开始搞乱 IEEE 舍入模式之前。因此标准避免要求它,因此它不必定义编译时评估何时以及如何不同于运行时评估。

正如 Brian 提到的,C++0x 将通过 constexpr 解决这个问题。如果我对最初的动机是正确的,那么大概 10 年的时间已经足够解决指定这些东西的困难了。

【讨论】:

愚蠢的标准。他们可以允许加倍。 好吧,具有讽刺意味的是,标准中有任何决定是为了避免给编译器编写者带来负担,但关键字 export 仍然存在。但我认为出口是错误的,也是通常规则的例外。在您确定规范是一致的并且可以在 C++ 想去的任何地方实现之前,最好不要指定任何东西。 "但是编译器在目标设备上模拟浮点硬件可能非常困难。"当然,这对于任何常量表达式的优化都是正确的,而不仅仅是那些内联常量。此外,无论如何都不能保证浮点的保真度,因为寄存器和内存中的表示可能不同,因此您获得的结果完全取决于编译器优化设置。事实上,在同一个程序中,相同的计算可能会计算出稍微不同的值。 但是整数常量表达式不是比常量表达式“更恒定”吗?期望它们比任何旧的常量表达式表现得更好。不幸的是,我不记得我第一次在哪里读到这个想法,即浮动的行为不够好,不能像 ICE 一样。如果那里有权威的理由,一定有人引用它。我没有The Design and Evolution of C++的副本,或者我会先看看那里。【参考方案2】:

Example::OneExample::Two 都应该为您编译,并且它们确实在您所说的相同环境中为我编译(VS 2008)。

我不相信Example::ThreeExample::Four 应该在标准C++ 中编译,但我认为有一个gcc 扩展允许它。 Example::Five 不应该编译。

您可以在结构声明之后像这样初始化它们,通常在您的源文件中:

const float Example::Three = 2000.0f;
const double Example::Four = 3000.0;
const string Example::Five = "Hello";

这是最便携的方法,即使您的编译器允许您在声明中定义 Example::ThreeExample::Four,我也建议您这样做。

另一种选择是简单地从相同类型的静态函数返回值。

struct Example

    //...
    static double Four()  return  = 3000.0; 
    //...
;

This answer 也讨论了一个可能的原因。This answer 讨论了即将到来的 C++ 标准将如何通过 constexpr 提供帮助

【讨论】:

是的;我知道这一点。我想知道是否有技术原因或真的有任何理由必须这样做。 有人知道标准对此有何规定吗?【参考方案3】:

#1 和 2 符合标准。不幸的是,一些编译器根本不符合。这就是为什么 Boost 设计者不得不引入像 BOOST_STATIC_CONSTANT 这样烦人的宏来生成可移植库的原因。如果您不想在 .cpp 文件中定义常量,可移植的解决方法是使用 enum。虽然,显然在那种情况下你不能保证类型,你不能使用浮点数。

【讨论】:

【参考方案4】:

在 C++98 中,只有静态 const 成员 可以初始化整数类型 在课堂上,初始化器必须 是一个常数表达式。这些 限制确保我们可以做到 在编译时初始化。

见In-class member initializers。

§9.4.2 静态数据成员

如果静态数据成员是 const 整型或 const 枚举类型,它在类定义中的声明可以 指定一个常量初始化器,它应该是一个整数常量表达式(5.19)。在这种情况下,该成员可以出现 在整数常量表达式中。如果在程序中使用该成员,则该成员仍应在命名空间范围内定义,并且 命名空间范围定义不应包含初始化程序。

【讨论】:

如果你可以在编译时初始化int,你肯定可以在同样的情况下初始化short,不是吗?所以它没有解释这个限制的原因【参考方案5】:

在VS2008下出现如下错误:

1>.\Weapon Identification SystemDlg.cpp(223) : error C2864: 'Example::Three' : only static const integral data members can be initialized within a class
1>.\Weapon Identification SystemDlg.cpp(224) : error C2864: 'Example::Four' : only static const integral data members can be initialized within a class
1>.\Weapon Identification SystemDlg.cpp(225) : error C2864: 'Example::Five' : only static const integral data members can be initialized within a class

这很糟糕,但我想如果你的编译器也拒绝,你就不必这样做......我不知道这是规范的事情,但我相信有人会纠正我......

【讨论】:

【参考方案6】:

关于浮点初始化器,C++98 规范有这样的说法(5.19):

浮动文字只有在转换为整数或枚举类型时才能出现。

【讨论】:

【参考方案7】:

正如其他人发现的那样,C++ 标准禁止使用浮点值初始化静态 const 成员。

至少据我了解,这是有一个相当简单的原因。有一种感觉(至少部分合理)应该允许实现动态调整浮点精度,因此可能直到运行时,实现才知道将从特定浮点产生/将产生的确切浮点值文字。事实上,这甚至有可能在执行过程中发生变化。

此功能确实存在于真实硬件中。例如,英特尔 x86 在浮点控制寄存器中有几个位,用于控制浮点计算的准确性。默认情况下,计算是在 80 位 long double 类型上完成的,并且仅根据请求四舍五入到类似于 64 位 double 或 32 位 float 的值。寄存器中的这些位可以在执行期间修改,因此(例如)在一个地方的“1.23”可以将变量初始化为一个值,而在程序的另一部分(在调整精度之后)可能会导致“1.23”在一个(稍微)不同的值。

至少据我所知,这仍然是理论上的可能性,至少在大多数典型机器上是这样。尽管英特尔硬件允许动态调整 FP 精度,但我不知道有任何编译器(甚至英特尔的)在翻译 FP 文字时尝试考虑这种调整(尽管英特尔的编译器至少支持 80 位长双重类型)。

【讨论】:

【参考方案8】:

正如其他人所指出的,您的编译器在某些情况下会损坏。但我从来没有真正理解浮点类型不允许使用它的原因,除了“标准这么说”。似乎没有好的技术原因。

【讨论】:

以上是关于为啥不允许“内联”静态常量,除了整数?的主要内容,如果未能解决你的问题,请参考以下文章

Android - 为啥人们反复引用静态上下文内联,而不是在 Method() 中传递一次?

为啥 C++17 中的全局内联变量和静态内联成员需要守卫?

为啥 std::stringstream 在静态内联时不能默认构造?

为啥常量整数指针指向允许的非常量整数?

为啥一个类允许拥有自己的静态成员,而不是非静态成员?

为啥 C# 不允许静态方法实现接口?