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

时间:2012-07-17 12:26:00

标签: c++ gcc g++ static-members constexpr

要求

我想要从constexpr函数计算的constexpr值(即编译时常量)。我希望这两个范围都限定在类的名称空间中,即静态方法和类的静态成员。

首次尝试

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

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

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

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

error: field initializer is not constant

第二次尝试

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

class C2 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar;
};
constexpr int C2::bar = C2::foo(sizeof(int));

g++-4.5.3会在没有投诉的情况下编译。不幸的是,我的其他代码使用了一些基于范围的for循环,所以我必须至少有4.6。现在我仔细观察support list,看来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]

这听起来很奇怪。这里的“{1}}”有何不同之处?我不想添加constexpr,因为我更喜欢我的其他代码被严格检查。将-fpermissive实现移到类体之外没有明显的效果。

预期答案

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

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

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

4 个答案:

答案 0 :(得分:17)

标准要求(第9.4.2节):

  

可以使用static说明符在类定义中声明文字类型的constexpr数据成员;如果是这样,它的声明应指定一个大括号或等于初始化,其中作为赋值表达式的每个 initializer-clause 是一个不断表达。

在您的“第二次尝试”和Ilya答案中的代码中,声明没有大括号或等于初始值

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

这是不可能的,因为遗憾的是,标准排除了在类完成的任何上下文中初始化静态constexpr数据成员。 9.2p2中 brace-or-equal-initializers 的特殊规则仅适用于非静态数据成员,但这个成员是静态的。

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

  

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

考虑:

class C1
{
  constexpr static int foo(int x) { return x + bar; }
  constexpr static int bar = foo(sizeof(int));
};

答案 1 :(得分:4)

1)Ilya的示例应该是无效的代码,因为静态constexpr数据成员栏是在线外初始化的,违反了标准中的以下语句:

  

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

2)MvG问题中的代码:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

在我看来是有效的,直觉上人们会期望它能够正常工作,因为静态成员foo(int)是由 bar 启动的时间处理定义的(假设自上而下的处理)。 一些事实:

  • 我同意虽然 class C1 在调用foo时没有完成(基于9.2p2)但是 类的完整性或不完整性C1 没有说明foo是否被定义为标准。
  • 我确实在标准中搜索了成员函数的定义,但没有找到任何内容。
  • 因此,如果我的逻辑有效,Ben提到的陈述不适用于此:

      

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

  •   
  3)Ben给出的最后一个例子,简化:

class C1
{
  constexpr static int foo() { return bar; }
  constexpr static int bar = foo();
};

看起来无效但出于不同的原因而不仅仅是因为 foo bar 的初始化程序中被调用。逻辑如下:

  • foo()静态constexpr成员栏的初始值设定项中调用,因此它必须是常量表达式(9.4.2 p3)。
  • 因为它是constexpr函数的调用,所以函数调用替换(7.1.5 p5)启动。
  • 它们不是函数的参数,所以剩下的就是“将生成的返回表达式或braced-init-list隐式转换为函数的返回类型,就好像通过复制初始化一样。” (7.1.5 p5)
  • 返回表达式只是 bar ,这是一个左值,需要进行左值到右值的转换。
  • 但是(5.19 p2)中的子弹9 bar 不满足因为尚未初始化:

      
        
    • 左值 - 右值转换(4.1),除非它适用于:   
          
      • 一个整数或枚举类型的glvalue,它引用一个带有前面初始化的非易失性const对象,用一个常量表达式初始化。
      •   
    •   
  • 因此 bar 的左值到右值转换不会产生一个不符合(9.4.2 p3)要求的常量表达式。

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

    使用参数调用constexpr函数,当被函数调用替换(7.1.5)替换时,不生成常量表达式

  •   

答案 2 :(得分:3)

#include <iostream>

class C1 
{
public:
    constexpr static int foo(constexpr int x)
    { 
        return x + 1;
    }

    static constexpr int bar;
};

constexpr int C1::bar = C1::foo(sizeof(int));

int main()
{
    std::cout << C1::bar << std::endl;
    return 0;
}

这样的初始化效果很好,但仅适用于clang

答案 3 :(得分:2)

这里的问题可能与类中声明/定义的顺序有关。众所周知,即使在类中声明/定义之前,您也可以使用任何成员。

当你在类中定义de constexpr值时,编译器没有可用的constexpr函数,因为它在类中。

或许,与这个想法相关的Philip回答是理解这个问题的一个好点。

请注意这段编译没有问题的代码:

constexpr int fooext(int x) { return x + 1; }
struct C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = fooext(5);
};

constexpr static int barext = C1::foo(5);