C++ constexpr 使用静态函数初始化静态成员
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11522399/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
constexpr initializing static member using static function
提问by MvG
Requirements
要求
I want a constexpr
value (i.e. a compile-time constant) computed from a constexpr
function. And I want both of these scoped to the namespace of a class, i.e. a static method and a static member of the class.
我想要一个constexpr
从constexpr
函数计算的值(即编译时常量)。而且我希望这两个范围都在类的命名空间内,即类的静态方法和静态成员。
First attempt
第一次尝试
I first wrote this the (to me) obvious way:
我首先用(对我来说)显而易见的方式写了这个:
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
says to that:
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
complains:
g++-4.6.3 -std=gnu++0x
抱怨:
error: field initializer is not constant
Second attempt
第二次尝试
OK, I thought, perhaps I have to move things out of the class body. So I tried the following:
好吧,我想,也许我必须把东西移出类体。所以我尝试了以下方法:
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
will compile that without complaints. Unfortunately, my other code uses some range-based for
loops, so I have to have at least 4.6. Now that I look closer at the support list, it appears that constexpr
would require 4.6 as well. And with g++-4.6.3
I get
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]
This sounds really strange to me. How do things “differ in constexpr
” here? I don't feel like adding -fpermissive
as I prefer my other code to be rigurously checked. Moving the foo
implementation outside the class body had no visible effect.
这对我来说听起来很奇怪。事情在constexpr
这里“不同”如何?我不想添加,-fpermissive
因为我更喜欢严格检查我的其他代码。将foo
实现移到类体之外没有明显的效果。
Expected answers
预期答案
Can someone explain what is going on here? How can I achieve what I'm attempting to do? I'm mainly interested in answers of the following kinds:
有人可以解释这里发生了什么吗?我怎样才能实现我正在尝试做的事情?我主要对以下类型的答案感兴趣:
- A way to make this work in gcc-4.6
- An observation that later gcc versions can deal with one of the versions correctly
- A pointer to the spec according to which at least one of my constructs shouldwork, so that I can bug the gcc developers about actually getting it to work
- Information that what I want is impossible according to the specs, preferrably with some insigt as to the rationale behind this restriction
- 一种在 gcc-4.6 中完成这项工作的方法
- 观察到后来的 gcc 版本可以正确处理其中一个版本
- 一个指向规范的指针,根据该规范,我的至少一个构造应该可以工作,以便我可以让 gcc 开发人员真正让它工作
- 根据规范,我想要的信息是不可能的,最好是对此限制背后的基本原理有所了解
Other useful answers are welcome as well, but perhaps won't be accepted as easily.
其他有用的答案也是受欢迎的,但可能不会那么容易被接受。
采纳答案by Ben Voigt
The Standard requires (section 9.4.2):
该标准要求(第 9.4.2 节):
A
static
data member of literal type can be declared in the class definition with theconstexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializerin which every initializer-clausethat is an assignment-expressionis a constant expression.
甲
static
文字类型的数据成员可以在类定义与声明constexpr
说明符; 如果是这样,它的声明应指定一个大括号或等号初始值设定项,其中每个作为赋值表达式的初始值设定项子句都是一个常量表达式。
In your "second attempt" and the code in Ilya's answer, the declaration doesn't have a brace-or-equal-initializer.
在您的“第二次尝试”和 Ilya 的答案中的代码中,声明没有大括号或相等的初始化程序。
Your first code is correct. It's unfortunate that gcc 4.6 isn't accepting it, and I don't know anywhere to conveniently try 4.7.x (e.g. ideone.com is still stuck on gcc 4.5).
你的第一个代码是正确的。不幸的是 gcc 4.6 不接受它,而且我不知道在哪里可以方便地尝试 4.7.x(例如 ideone.com 仍然停留在 gcc 4.5 上)。
This isn't possible, because unfortunately the Standard precludes initializing a static constexpr
data member in any context where the class is complete. The special rule for brace-or-equal-initializersin 9.2p2 only applies to non-staticdata members, but this one is static.
这是不可能的,因为不幸的是,标准禁止constexpr
在类完成的任何上下文中初始化静态数据成员。9.2p2 中大括号或相等初始化器的特殊规则仅适用于非静态数据成员,但这是静态的。
The most likely reason for this is that constexpr
variables have to be available as compile-time constant expressions from inside the bodies of member functions, so the variable initializers are completely defined before the function bodies -- which means the function is still incomplete (undefined) in the context of the initializer, and then this rule kicks in, making the expression not be a constant expression:
最可能的原因是constexpr
变量必须在成员函数体内部作为编译时常量表达式可用,因此变量初始化器在函数体之前完全定义——这意味着函数仍然不完整(未定义)在初始值设定项的上下文中,然后此规则生效,使表达式不是常量表达式:
an invocation of an undefined
constexpr
function or an undefinedconstexpr
constructor outside the definition of aconstexpr
function or aconstexpr
constructor;
在
constexpr
函数或constexpr
构造函数的定义之外调用未定义的函数或未定义的构造constexpr
函数constexpr
;
Consider:
考虑:
class C1
{
constexpr static int foo(int x) { return x + bar; }
constexpr static int bar = foo(sizeof(int));
};
回答by user1574647
1) Ilya's example should be invalid code based on the fact that the static constexpr data member baris initialized out-of-line violating the following statement in the standard:
1) Ilya 的示例应该是无效代码,因为静态 constexpr 数据成员 bar被初始化为违反标准中的以下语句:
9.4.2 [class.static.data] p3: ... A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression.
9.4.2 [class.static.data] p3: ... 可以在类定义中使用 constexpr 说明符声明文字类型的静态数据成员;如果是这样,它的声明应指定一个大括号或等号初始化器,其中每个作为赋值表达式的初始化器子句都是一个常量表达式。
2) The code in MvG's question:
2)MvG问题中的代码:
class C1 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar = foo(sizeof(int));
};
is valid as far as I see and intuitively one would expect it to work because the static member foo(int)is defined by the time processing of barstarts (assuming top-down processing). Some facts:
就我所见和直觉上来说是有效的,因为静态成员 foo(int)是由bar开始的时间处理定义的(假设自上而下处理)。一些事实:
- I do agree though that class C1is not complete at the point of invocation of foo (based on 9.2p2) butcompleteness or incompleteness of the class C1says nothing about whether foo is defined as far as the standard is concerned.
- I did search the standard for the definedness of member functions but didn't find anything.
- So the statement mentioned by Ben doesn't apply here if my logic is valid:
an invocation of an undefined constexpr function or an undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor;
- 我确实同意,尽管在调用 foo 时类 C1并不完整(基于 9.2p2),但类 C1 的完整性或不完整性并没有说明 foo 是否就标准而言是定义的。
- 我确实搜索了成员函数定义的标准,但没有找到任何东西。
- 因此,如果我的逻辑有效,Ben 提到的陈述不适用于这里:
在 constexpr 函数或 constexpr 构造函数的定义之外调用未定义的 constexpr 函数或未定义的 constexpr 构造函数;
class C1
{
constexpr static int foo() { return bar; }
constexpr static int bar = foo();
};
looks invalid but for different reasons and not simply because foois called in the initializer of bar. The logic goes as follows:
看起来无效,但出于不同的原因,而不仅仅是因为foo在bar的初始值设定项中被调用。逻辑如下:
- foo()is called in the initializer of the static constexpr member bar, so it has to be a constant expression (by 9.4.2 p3).
- since it's an invocation of a constexpr function, the Function invocation substitution(7.1.5 p5) kicks in.
- Their are no parameters to the function, so what's left is "implicitly converting the resulting returned expression or braced-init-list to the return type of the function as if by copy-initialization." (7.1.5 p5)
- the return expression is just bar, which is a lvalue and the lvalue-to-rvalue conversion is needed.
but by bullet 9 in (5.19 p2) which bardoes notsatisfy because it is not yet initialized:
- an lvalue-to-rvalue conversion (4.1) unless it is applied to:
- a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression.
- an lvalue-to-rvalue conversion (4.1) unless it is applied to:
hence the lvalue-to-rvalue conversion of bardoes not yield a constant expression failing the requirement in (9.4.2 p3).
- so by bullet 4 in (5.19 p2), the call to foo()is not a constant expression:
an invocation of a constexpr function with arguments that, when substituted by function invocation substitution (7.1.5), do not produce a constant expression
- foo()在静态 constexpr 成员 bar的初始化程序中被调用,因此它必须是一个常量表达式(9.4.2 p3)。
- 因为它是对 constexpr 函数的调用,所以函数调用替换(7.1.5 p5) 开始了。
- 它们没有函数的参数,所以剩下的是“将结果返回的表达式或花括号初始化列表隐式转换为函数的返回类型,就像通过复制初始化一样。” (7.1.5 p5)
- 返回表达式只是bar,它是一个左值,需要进行左值到右值的转换。
但通过在子弹9(5.19 P2),该杆并不满足,因为它尚未初始化:
- 左值到右值的转换 (4.1) 除非它应用于:
- 整数或枚举类型的泛左值,它引用具有前面初始化的非易失性 const 对象,用常量表达式初始化。
- 左值到右值的转换 (4.1) 除非它应用于:
因此bar的左值到右值转换不会产生不符合 (9.4.2 p3) 要求的常量表达式。
- 因此,根据 (5.19 p2) 中的第 4 项,对foo()的调用不是常量表达式:
调用带有参数的 constexpr 函数,当被函数调用替换 (7.1.5) 替换时,不会产生常量表达式
回答by Ilya Lavrenov
#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;
}
Such initialization works well but only on clang
这种初始化效果很好,但只适用于 clang
回答by EFenix
Probably, the problem here is related to the order of declaration/definitions in a class. As you all know, you can use any member even before it is declared/defined in a class.
可能,这里的问题与类中声明/定义的顺序有关。众所周知,您甚至可以在类中声明/定义任何成员之前使用它。
When you define de constexpr value in the class, the compiler does not have the constexpr function available to be used because it is inside the class.
当您在类中定义 de constexpr 值时,编译器没有可用的 constexpr 函数,因为它在类内部。
Perhaps, Philipanswer, related to this idea, is a good point to understand the question.
也许,与此想法相关的Philip回答是理解问题的好点。
Note this code which compiles without problems:
请注意此代码编译没有问题:
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);