我想要从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
实现移到类体之外没有明显的效果。
有人可以解释这里发生了什么吗?我怎样才能实现我的目标?我主要对以下几种答案感兴趣:
其他有用的答案也是受欢迎的,但也许不会轻易接受。
答案 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 启动的时间处理定义的(假设自上而下的处理)。 一些事实:
调用未定义的constexpr函数或未定义的函数 Constexpr构造函数在constexpr函数的定义之外 或constexpr构造函数;
class C1
{
constexpr static int foo() { return bar; }
constexpr static int bar = foo();
};
看起来无效但出于不同的原因而不仅仅是因为 foo 在 bar 的初始化程序中被调用。逻辑如下:
但是(5.19 p2)中的子弹9 bar 不满足因为尚未初始化:
- 左值 - 右值转换(4.1),除非它适用于:
- 一个整数或枚举类型的glvalue,它引用一个带有前面初始化的非易失性const对象,用一个常量表达式初始化。
因此 bar 的左值到右值转换不会产生一个不符合(9.4.2 p3)要求的常量表达式。
使用参数调用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);