【问题标题】:Confusion about constant initialization关于常量初始化的困惑
【发布时间】:2018-04-05 15:02:59
【问题描述】:

cppref 中,它给出了常量初始化的语法:

static T & ref = constexpr; 
static T object = constexpr;    

这是我的两个问题:

第一季度

没有const 的左值引用T & 怎么会绑定到constexptr,它是常量且不可修改的?

我尝试提供一些示例但失败了:

 static int& ref = 6; //error, need a `const`
 constexpr int a = 6; static int& ref = a; //error, need a `const`  

第二季度

常量初始化的对象必须是const/static吗?在the standard里面说:

如果具有静态或线程存储持续时间的变量或临时对象由实体的常量初始化程序初始化,则执行常量初始化。

这里标准没有指定obj为const-qualified/static-qualified

【问题讨论】:

  • 静态必须用constexpr 语句初始化,但这并不意味着静态本身必须是constexpr。这有效:static int a=6; static int& ref=a; 编辑: 虽然基于措辞我认为这不再符合常量初始化的条件。
  • 您的编辑似乎提出了另一个问题。也许最好提出一个新问题而不是编辑。
  • @xskxzr 但是我已经收到了这两个问题的答案。我再次对其进行了编辑以使其更加清晰。

标签: c++ c++11 constexpr


【解决方案1】:

这句话的意思是

static int a;
static int & ref = a;
static_assert(&a == &ref, "");

没问题。初始化是常量初始化的一种形式,因为a 在评估为左值时是一个常量表达式(但仅作为左值!),因此,&a == &ref 是一个常量表达式,其计算结果为true .

比较一下

void f() {
  int a;
  static int & ref = a;
  static_assert(&a == &ref, "");
}

这是无效的。尽管ref 的初始化在技术上是有效的,但只要函数返回,它就会变成一个悬空引用。下次输入函数时,会创建一个新的int a 对象。因此,&a == &ref 不能保证评估为true。它不是一个常量表达式,如果求值,会产生未定义的行为。

【讨论】:

  • 常量初始化的对象有必要为const吗?
  • @bigxiao 没有。只是普通的static int i = 1; 也是常量初始化。
  • 只要初始化器和变量都具有 静态持续时间(或者可以在编译时评估),它就是常量 init。对吗?
  • @bigxiao 我不确定你的意思。在static int i = 1; 中,初始化程序1 是prvalue,因此没有静态存储持续时间。在static int i; static int j = i; 中,初始化程序i 具有静态存储持续时间,但这仍然不是常量初始化。
  • @bigxiao 我也编辑了我的评论来解决这个问题。
【解决方案2】:

混淆是由于命名:常量初始化[basic.start.static]/2常量表达式[expr.const]中的术语常量表示无需编译器的英勇努力(1)即可在编译时评估。这与常量对象的概念不同,常量对象的值一旦定义就不会改变。

为了说明编译时评估的限制,让我们看一下这段代码的汇编:

//case 0
int i0 = 5;
int j0 = i0;//no compil-time initialized
//case 1
const int i1=5; 
int j1=i1; //compil-time initialized
//case 2
extern const int i2=5; 
int j2=i2; //compile-time initialized
//case 3
extern const int i3; 
int j3=i3; //no compil-time initialization
//case 4
extern const int i4; 
int j4=i4; //no compil-time initialization
const int i4=5;

gcc 7.3 生成的程序集:

_GLOBAL__sub_I_example.cpp: # @_GLOBAL__sub_I_example.cpp
  mov eax, dword ptr [rip + i0]
  mov dword ptr [rip + j0], eax
  mov eax, dword ptr [rip + i3]
  mov dword ptr [rip + j3], eax
  mov dword ptr [rip + j4], 5
  ret

发生了什么:

  • case 0,j0 在编译时没有被初始化,因为 i0 不是一个常数。[expr.constant]/2.7
  • 案例 1 和 2 是编译时初始化的,因为它们符合先前规则 [expr.constant]/2.7.3 的例外情况。
  • 案例 3 和案例 4、j3 和 j4 在编译时未初始化,因为它们不符合最后一条规则异常,因为它们没有预先初始化,(至少它可以在链接时解决,但这会是英勇的努力或依赖于实施质量)

(1) 原则是语言不能太复杂编译。我刚刚回收了 template argument deduction 标准的措辞,其中 heroic effort 这个词出现了乱七八糟的情况。同样的原则也适用于定义什么是常量表达式。

【讨论】:

  • static a=1;static b=a 怎么样。b 在编译时可以评估吗?
  • @bigxiao 根据标准a 不是一个常量表达式,但是由于“as-if”规则,gcc 在编译时对其进行了初始化。不过您可以尝试使用 a 实例化模板,编译将失败:std::integral_constant<int,a>
  • 你说“常量表达式意味着在编译时可评估”,我认为它不包括一些特殊情况。例如,ab在编译时都是可评估的,但是b不是常量初始化a不是常量表达式a常量初始化
  • @bigxiao,gcc 不要对非静态情况使用 as-if 规则,因为它不知道变量是否会在其他翻译单元中更改,请考虑这种情况:@ 987654336@和file2.cpp; extern int i; int f(){ return ++i;}
  • @bigxiao 我写过“无需编译器的英勇努力即可在编译时评估”。我删除了围绕英勇努力的括号。什么是英勇的努力可以从 [expr.constant] 中扣除。我展示的示例的目标正是向您展示即使 you 能够在编译时评估表达式的值,编译器也可以不这样做,这在 [ expr.constant]
【解决方案3】:

“常量初始化”意味着初始化器是一个常量表达式。表达式和变量都不需要是 const 限定的。

int x = 6; 在文件范围内是常量初始化。

参考:C++17 [basic.start.static]/2:

常量初始化如果具有静态或线程存储持续时间的变量或临时对象由实体的常量初始化程序初始化。

【讨论】:

  • constant 这个词看起来像const-qualified,具有误导性。constant 这个词实际上是指静态持续时间吗?
  • 只要初始化器和变量都是静态持续时间,它就是常量 init。我说的对吗?
  • @bigxiao 是的,或者线程。并且初始化器必须是常量表达式
【解决方案4】:

在您参考的页面中您可以阅读

设置静态常量的初始值

我把你的注意力放在常量

所以T 必须是常量类型。

所以int const 可以; constexpr int 可以,因为 constexpr 暗示 const; int 没有 const(或没有 constexpr 暗示 const)是错误的。

【讨论】:

  • 我不会说没有constint错误,只是说它会使用某种不是“持续初始化”的其他形式的初始化。
  • @0x5453 - 不确定理解你的意思,无论如何......你是对的:static 变量不一定是常量;所以static int a=6; static int& ref=a; 可以;但是 OP 在 cppreference 中要求precise page 是关于“静态常量”的初始化。所以static int a=6;static int& ref=a; 与该页面不匹配。
  • Constant initialization 是标准中定义的术语。我猜 cppreference 中的描述不准确。
  • 但是标准没有指定obj为constant,请看我编辑的问题。
猜你喜欢
  • 2019-02-17
  • 2016-06-30
  • 1970-01-01
  • 1970-01-01
  • 2013-01-10
  • 2018-04-25
  • 2014-04-13
  • 1970-01-01
  • 2021-10-26
相关资源
最近更新 更多