【问题标题】:constexpr initializing static member using static functionconstexpr 使用静态函数初始化静态成员
【发布时间】:2012-07-16 08:50:43
【问题描述】:

要求

我想要一个从 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]

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

预期答案

有人可以解释这里发生了什么吗?我怎样才能实现我正在尝试做的事情?我主要对以下类型的答案感兴趣:

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

也欢迎其他有用的答案,但可能不会那么容易被接受。

【问题讨论】:

  • 在类定义之外移动 foo 似乎可以解决 gcc 6.1 中的问题:请参见此处:link

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


【解决方案1】:
#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 上

【讨论】:

  • 由于我在多个地方使用 gcc 特定的东西,因此切换到 clang 感觉不合适。虽然我仍在等待 gcc 的答案,但 clang 支持这一点的事实可能表明 gcc 的实现中存在错误。欢迎任何来自规范的权威答案。
  • gcc 是正确的(对于这个代码),clang 是错误的(或者这是一个不可移植的“扩展”)。
【解决方案2】:

标准要求(第 9.4.2 节):

可以在类定义中使用constexpr 说明符声明文字类型的static 数据成员;如果是这样,它的声明应指定一个 brace-or-equal-initializer,其中作为 assignment-expression 的每个 initializer-clause 都是一个常量表达式。

在您的“第二次尝试”和 Ilya 回答中的代码中,声明没有 brace-or-equal-initializer

你的第一个代码是正确的。不幸的是 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));
};

【讨论】:

  • liveworkspace.org/code/b9764c378d246308970e50eb6378c3aa 但是如果用 const 替换 constexpr 即使使用 gcc 也能正常工作。
  • @Ilya:谢谢。似乎 g++ 4.7.1 仍然是错误的 wrt constexpr 静态成员函数。
  • 所以我想我最终会为此提交一个 gcc 错误,使用您的 qutoe 作为 应该 工作的指示。非常感谢!
  • @MvG:我在答案中添加了应该与错误报告相关的内容。
  • 归档为bug 54002。感谢您参与本次调查,欢迎抄送该错误报告。
【解决方案3】:

1) Ilya 的示例应该是无效的代码,因为 static constexpr 数据成员 bar 被超出标准初始化,违反了以下标准:

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

据我所见是有效的,直觉上人们会期望它可以工作,因为 static 成员 foo(int) 是由 bar 开始的时间处理定义的(假设自上而下处理)。 一些事实:

  • 我确实同意,尽管 class C1 在调用 foo 时并不完整(基于 9.2p2)但是 class 的完整性或不完整性就标准而言,C1 没有说明 foo 是否被定义。
  • 我确实搜索了成员函数定义的标准,但没有找到任何东西。
  • 所以如果我的逻辑是有效的,Ben 提到的陈述在这里不适用:

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

3)Ben给出的最后一个例子,简化:
class C1
{
  constexpr static int foo() { return bar; }
  constexpr static int bar = foo();
};

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

  • foo()static constexpr 成员 bar 的初始化程序中被调用,因此它必须是一个常量表达式(按 9.4.2 p3)。
  • 因为它是一个 constexpr 函数的调用,所以 函数调用替换 (7.1.5 p5) 开始了。
  • 它们不是函数的参数,所以剩下的就是“隐式地将结果返回的表达式或花括号初始化列表转换为函数的返回类型,就像通过复制初始化一样”。 (7.1.5 p5)
  • 返回表达式就是bar,是一个左值,需要左值到右值的转换。
  • 但在 (5.19 p2) 中的第 9 条 bar 不满足,因为它尚未初始化:

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

  • 因此,根据 (5.19 p2) 中的第 4 条,对 foo() 的调用不是常量表达式:

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

【讨论】:

    【解决方案4】:

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

    当您在类中定义 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);
    

    【讨论】:

      猜你喜欢
      • 2016-10-15
      • 1970-01-01
      • 2018-11-11
      • 2015-05-29
      • 1970-01-01
      • 1970-01-01
      • 2019-07-05
      • 1970-01-01
      • 2021-08-07
      相关资源
      最近更新 更多