由于定义顺序,“令人惊讶”的常量初始化

时间:2011-10-02 11:00:39

标签: c++ initialization const c++11 constexpr

在阅读slides about constexpr时,介绍是关于“令人惊讶的动态初始化与consts”。例子是

struct S {
    static const int c;
};
const int d = 10 * S::c;
const int S::c = 5;

唉,音轨缺失了,音符也是如此,所以我只能猜到这里的含义。

d是否“令人惊讶地”动态初始化是不正确的,因为S::cd之前定义了 S::c声明d之前可能还不够,编译器需要完整的定义,对吧?

那就是说,我怀疑,在下面的例子中, d 静态初始化?

struct S {
    static const int c;
};
const int S::c = 5;
const int d = 10 * S::c;  // now _after_ defn of S::c

在C ++ 11中,需要constexpr 进行完全静态初始化? S::cd或两者都有?

4 个答案:

答案 0 :(得分:3)

在第一个示例中,d未通过常量表达式初始化,因为S::c不是

  

具有先前初始化的非易失性const对象,   用常量表达式初始化

(参见C ++ 11 [expr.const] p2,lvalue-to-rvalue转换的子弹),因为S::c的初始化不在d的初始化之前。因此,静态初始化将用于S::c(因为它由常量表达式初始化),但动态初始化可用于d

由于静态初始化在动态初始化之前,d将由其动态初始化器初始化为50。允许编译器将d的动态初始化转换为静态初始化,但如果确实如此,则必须生成d如果每个可能使用动态初始化的变量都具有的值,实际上,用过动态初始化。在这种情况下,d无论如何都会初始化为50。有关详细信息,请参阅C ++ 11 [basic.start.init] p2。

无法将constexpr添加到第一个示例,以保证d使用静态初始化;为此,您必须重新排序初始化。但是,添加constexpr将为第一个示例生成诊断,这将至少允许您确保动态初始化使用(您获得静态初始化或编译错误)。 / p>

您可以更新第二种情况,以确保按如下方式使用静态初始化:

struct S {
    static const int c; // do not use constexpr here
};
constexpr int S::c = 5;
constexpr int d = 10 * S::c;

在不是定义的变量声明上使用constexpr,或者在不包含初始值设定项的变量声明上使用const是错误的,所以constexpr,而不是{{ 1}}必须在struct S的定义中使用。此规则有一个例外,即在使用类中指定的初始值设定项定义文字非整数类型的static constexpr数据成员时:

struct T { int n; };
struct U {
    static constexpr T t = { 4 };
};
constexpr T U::t;

在这种情况下,必须在类的定义中使用constexpr,以便允许提供初始化程序,并且必须在静态数据成员的定义中使用constexpr,为了允许在常量表达式中使用它。

答案 1 :(得分:2)

我认为3.6.2中规定的确定静态初始化何时发生的规则不包括d的初始化,因此动态初始化。另一方面,S::c确实是静态初始化的(因为5是一个常量表达式)。由于所有静态初始化都在动态初始化之前发生,因此您将获得预期的结果。

要使d符合静态初始化条件,必须使用常量表达式对其进行初始化。这反过来迫使您编写S::c内联:

struct S { static constexpr int c = 5; };

const int d = S::c; // statically initialized

请注意,该标准允许动态初始化被静态初始化替换,这就是为什么重新排序原始示例中的两行将导致两种不同类型的初始化。正如TonyK指出的那样,您可以在静态情况下使用array[d],但不能在动态情况下使用constexpr,因此您可以检查正在发生的情况。使用{{1}}方法,您可以保证进行静态初始化,而不必依赖可选的编译器行为。

答案 2 :(得分:2)

对于静态初始化,粗略地说,需要一个常量表达式初始化器。

要成为一个常量表达式,粗略地说,变量需要是const类型,并且具有一个带有常量表达式的先前初始化。

在第一个示例d中,初始化程序不是常量表达式,因为S::c不是一个(它没有先前的初始化)。因此,d不是静态初始化的。

在第二个例子中d的初始化器是一个常量表达式,一切正常。

我正在简化问题。在完全正式的标准中,这将是大约九倍。


对于constexpr说明符,没有对象具有来声明constexpr。这只是一个额外的错误检查。 (这是关于constexpr 对象,而不是constexpr 函数)。

如果您想要一些额外的错误保护(或许明天会有5个会开始更改其值?),可能会在第二个版本中声明S::c constexpr。添加{{1}第一个变种不可能有所帮助。

答案 3 :(得分:1)

您可以通过尝试声明数组来查明是否静态或动态初始化常量:

struct S {
    static const int c;
};
const int d = 10 * S::c; // (1)
const int S::c = 5;      // (2)

static char array[d];

此代码在g ++版本4.7.0中失败,因为d是动态初始化的。如果你交换(1)和(2),它会编译,因为现在d是静态初始化的。但我找不到另一种方法来解决它,使用constexpr