constexpr使用静态函数初始化静态成员

MvG*_*MvG 31 c++ gcc g++ static-members constexpr

要求

我想要一个constexprconstexpr函数计算的值(即编译时常量).我想要将这两个作用于类的命名空间,即静态方法和类的静态成员.

第一次尝试

我第一次写这个(对我来说)明显的方式:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};
Run Code Online (Sandbox Code Playgroud)

g++-4.5.3 -std=gnu++0x 对此说:

error: ‘static int C1::foo(int)’ cannot appear in a constant-expression
error: a function call cannot appear in a constant-expression
Run Code Online (Sandbox Code Playgroud)

g++-4.6.3 -std=gnu++0x 抱怨:

error: field initializer is not constant
Run Code Online (Sandbox Code Playgroud)

第二次尝试

好吧,我想,也许我必须把事情从课堂上移开.所以我尝试了以下方法:

class C2 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar;
};
constexpr int C2::bar = C2::foo(sizeof(int));
Run Code Online (Sandbox Code Playgroud)

g++-4.5.3将编译,没有投诉.不幸的是,我的其他代码使用了一些基于范围的for循环,所以我必须至少有4.6.现在我仔细观察支持列表,看起来constexpr也需要4.6.随着g++-4.6.3我的到来

3:24: error: constexpr static data member ‘bar’ must have an initializer
5:19: error: redeclaration ‘C2::bar’ differs in ‘constexpr’
3:24: error: from previous declaration ‘C2::bar’
5:19: error: ‘C2::bar’ declared ‘constexpr’ outside its class
5:19: error: declaration of ‘const int C2::bar’ outside of class is not definition [-fpermissive]
Run Code Online (Sandbox Code Playgroud)

这对我来说听起来很奇怪.constexpr这里的事情如何"不同"?我不想添加,-fpermissive因为我更喜欢我的其他代码被严格检查.将foo实现移到类体外部没有明显的效果.

预期的答案

有人能解释一下这里发生了什么吗?我怎样才能实现我的目标?我主要对以下几种答案感兴趣:

  • 一种在gcc-4.6中使用的方法
  • 观察到后来的gcc版本可以正确处理其中一个版本
  • 一个指向规范的指针,根据该规范,至少我的一个构造应该工作,这样我就可以让gcc开发人员知道它实际上是如何工作的
  • 根据规范,我想要的信息是不可能的,最好有一些关于这种限制背后的基本原理的信息

其他有用的答案也是受欢迎的,但也许不会轻易接受.

Ben*_*igt 19

标准要求(第9.4.2节):

static文字类型的数据成员可以在类定义与声明constexpr说明符; 如果是这样,它的声明应指定一个大括号或等于初始化器,其中作为赋值表达式的每个initializer子句都是一个常量表达式.

在你的"第二次尝试"和Ilya的答案中的代码中,声明没有括号或等于初始化器.

你的第一个代码是正确的.不幸的是gcc 4.6不接受它,我不知道在哪里方便地尝试4.7.x(例如,ideone.com仍然停留在gcc 4.5上).

这是不可能的,因为遗憾的是,标准排除了constexpr在类完成的任何上下文中初始化静态数据成员.9.2p2中的括号或等于初始化程序的特殊规则仅适用于非静态数据成员,但这一个是静态的.

最可能的原因是constexpr变量必须作为成员函数体内部的编译时常量表达式提供,因此变量初始值设定项在函数体之前完全定义 - 这意味着函数仍然不完整(未定义)在初始化程序的上下文中,然后此规则启动,使表达式不是常量表达式:

constexpr函数或constexpr构造函数的定义之外调用未定义的函数或未定义的构造constexpr函数constexpr;

考虑:

class C1
{
  constexpr static int foo(int x) { return x + bar; }
  constexpr static int bar = foo(sizeof(int));
};
Run Code Online (Sandbox Code Playgroud)


小智 5

1)Ilya的示例应该是无效的代码,这是因为以下事实:静态constexpr数据成员栏违反了标准中的以下声明而被离线初始化:

9.4.2 [class.static.data] p3:...可以使用constexpr说明符在类定义中声明文字类型的静态数据成员;如果是这样,则其声明应指定大括号或相等的初始化程序,其中每个作为赋值表达式的初始化程序子句都是一个常量表达式。

2)MvG问题中的代码:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};
Run Code Online (Sandbox Code Playgroud)

就我所见,它是有效的,并且直觉上可以预期它会起作用,因为静态成员foo(int)是由bar开始处理的时间定义的(假设自上而下进行处理)。一些事实:

  • 我确实同意,尽管在调用foo(基于9.2p2)时类C1尚未完成,但是类C1的完整性或不完整性并没有说明就标准而言是否定义了foo。
  • 我确实在标准中搜索了成员函数的定义性,但没有发现任何东西。
  • 因此,如果我的逻辑有效,那么Ben提到的声明就不适用于这里:

    在constexpr函数或constexpr构造函数的定义之外调用未定义的constexpr函数或未定义的constexpr构造函数;

3)Ben给出的最后一个示例简化了:

class C1
{
  constexpr static int foo() { return bar; }
  constexpr static int bar = foo();
};
Run Code Online (Sandbox Code Playgroud)

看起来是无效的,但由于不同的原因,不仅仅是因为在bar的初始化器中调用了foo。逻辑如下:

  • 静态constexpr成员bar的初始化程序中调用foo(),因此它必须是一个常量表达式(9.4.2 p3表示)。
  • 由于它是对constexpr函数的调用,因此将调用Function调用替换(7.1.5 p5)。
  • 它们不是函数的参数,因此剩下的就是“隐式地将结果返回的表达式或braced-init-list转换为函数的返回类型,就像通过复制初始化一样。” (7.1.5 p5)
  • 返回表达式只是bar,它是一个左值,需要从左值到右值的转换。
  • 但在(5.19 p2)中的项目符号9中,由于尚未初始化,该小节 满足:

    • 左值到右值转换(4.1),除非将其应用于:
      • 整数或枚举类型的glvalue,该值引用具有常量初始化的,具有先前初始化的非易失性const对象。
  • 因此,bar的左值到右值转换不会产生未满足(9.4.2 p3)中要求的常数表达式。

  • 因此,在(5.19 p2)中的项目符号4中,对foo()的调用不是常量表达式:

    带有参数的constexpr函数的调用,当被函数调用替换(7.1.5)替换时,不产生常量表达式