当前位置: 首页 > 知识库问答 >
问题:

为什么我们需要两个定义:积分常量表达式和转换常量表达式?

方波娃
2023-03-14

C 14中的§5.19/3定义了积分常量表达式和转换常量表达式:

整数常量表达式是整数或无作用域枚举类型的表达式,隐式转换为prvalue,其中转换的表达式是核心常量表达式。[注意:此类表达式可用作数组边界(8.3.4, 5.3.4),作为位字段长度(9.6),如果基础类型不固定,则可用作枚举器初始化器(7.2),并用作对齐(7.6.2)。-结束注释]类型T的转换常量表达式是一个表达式,隐式转换为类型T的prvalue,其中转换后的表达式是核心常量表达式,隐式转换序列仅包含用户定义的转换、左值-右值转换(4.1)、整数提升(4.5)和整数转换(4.7),而不是缩小转换(8.5.4)。[注意:此类表达式可以在new表达式(5.3.4)中使用,作为case表达式(6.4.2),如果底层类型是固定的,则作为枚举器初始值设定项(7.2),作为html" target="_blank">数组边界(8.3.4),以及作为整数或枚举非类型模板参数(14.3)。-结束注释]

也许我错过了什么,但我的第一印象是每个积分常量表达式都是一个转换的常量表达式。

编辑

我还认为这一段有一个错误:

而不是:

T类型的转换常量表达式是一个表达式,隐式转换为T类型的prvalue,...

应该是:

T型转换常量表达式是一个表达式,隐式转换为整型prvalue

此更改允许编译以下代码:

#include <iostream>
struct A { operator int() { return 5; } } a;

int main() {
    int b[a]{ 0, 1, 2, 3, 4 };
    std::cout << b[4] << '\n';
}

其中a在声明intb[a]{0,1,2,3,4}中是类型为a的转换常量表达式,隐式转换为整型(int)的prvalue,其中转换表达式5是核心常量表达式,隐式转换序列仅包含用户定义的转换。


共有3个答案

江智
2023-03-14

在讨论了Jerry Coffin和cpplearner提供的答案后,让我提议重写那些该死的规则,如下所示:

[expr.const] 5.20\3(修改)

整型常量表达式是整型或非作用域枚举类型的表达式,它隐式转换为同一类型的prvalue核心常量表达式,从而隐式转换序列仅包含左值到右值的转换。

[expr.const]5.20\4(已修改)

类型T的转换常量表达式是任何类型的表达式,该表达式隐式转换为类型T的常量表达式,从而隐式转换序列仅包含:

  • 用户定义的转换,
  • 左值到右值的转换,
  • 数组到指针的转换,
  • 函数到指针的转换,
  • 资格转换,
  • 积分促销,
  • 除窄化转换以外的积分转换,
  • 来自std::nullptr\u t
  • 来自std::nullptr\u t
  • 函数指针转换

以及引用绑定(如果有)直接绑定的位置。[注:此类表达式可用于<代码>新 表达式中,如大小写表达式、枚举器初始值设定项(如果基础类型是固定的)、数组边界和非类型模板参数。-结束注]

现在区别很明显,呃?还应该提醒的是,根据5.20\7; 4\5,在某些情况下可以使用文字类类型的表达式来代替积分常量表达式。

刘博文
2023-03-14

注意:此答案基于目前最新的标准草案,称为N4567。指出了它与C 11/14标准之间的一些差异。

就类类型而言,整数常量表达式和转换后的常量表达式是不同的。在C 98/03中,当类类型不能在这里使用时(因为当时没有constexr转换函数),确实没有T类型的转换常量表达式这样的术语。

对于整数常量表达式,目标类型未知。但对于类型T的转换常量表达式,目标类型已知为T,并且T不一定是整数或无作用域的枚举类型1

因此,为了编译整型常量表达式,编译器首先需要确定目标类型。如果表达式具有整型或非作用域枚举类型,那么显然目标类型就是表达式的类型。否则,如果表达式有一个文本类类型(让我们称这个类型为E),则使用以下过程2

编译器检查E3中的所有非显式转换函数。假设这些函数的结果类型形成一个集合s。如果S只包含一个整型或非作用域枚举类型(参考修饰符被去除,constvolatile限定符被忽略:const volatile int

(需要注意的是,在上述过程中,不会检查转换函数模板。)

因此,例如,如果一个类类型有两个转换函数,一个是constepr operator int,另一个是constepr operator long,则该类型不能在整型常量表达式中使用(目标类型是不可判定的)。然而,这种类型可以用在int类型的转换常量表达式中,或者用在long类型的转换常量表达式中。

在确定目标类型D后,应用重载解析以找到最合适的转换函数或函数模板,然后调用choosen转换函数(必须是constexpr)以生成类型D-这部分或多或少与类型为D的转换常量表达式相同。

在下面的示例中,Var{}是一个有效的整型常量表达式,但它是一个无效的转换常量表达式,类型为std::size_t(该示例受此问题启发)。

class Var
{
public:

    constexpr operator int ()
    { return 42; }

    template <typename T>
    constexpr operator T () = delete;
};

enum {
    x = Var{} // the initializer of `x` is expected to be an 
              // integral constant expression
              // x has value 42
};

int t[ Var{} ]; // the array bound is expected to be a
                // converted constant expression of type std::size_t
                // this declaration is ill-formed

N4567 5.20[expr.const]第7页

如果在需要整型常量表达式的上下文中使用文字类类型的表达式,则该表达式将在上下文中隐式转换(第4条)为整型或无范围枚举类型,且所选转换函数应为constexpr

N4567 4[conv]p5

某些语言结构需要转换为一个值,该值具有适合该结构的特定类型集之一。在这样的上下文中出现的类类型e的表达式e被称为上下文隐式转换为特定类型T,并且当且仅当e可以隐式转换为类型T时,其格式良好,该类型确定如下:e搜索返回类型为cv的非显式转换函数T或引用cvT,以便上下文允许T。只有一个这样的T

  1. 在C 11/14中,转换后的常量表达式只能是整数或枚举类型。N4268改变了这一点
  2. 在C 11中,没有这样的过程,而是要求“类类型应具有一个到整数或枚举类型的非显式转换函数,且转换函数应为constepr”N3323将其更改为当前的措辞
  3. C 14标准中没有“不明确”一词。它是由CWG 1981年添加的

鄂育
2023-03-14

这两个定义都是必要的,因为你可以用一个做一些事情,但不能用另一个。不,不是每个整数常量表达式都是真正的转换常量表达式。举个明显的例子,转换后的常量表达式禁止缩小转换,但整数常量表达式不允许。

因此我不能这样做:

enum x : char { a = 1024 };

然而,如果枚举的初始值设定项允许使用整型常量表达式,而不是转换后的常量表达式,那么这恰恰是允许的。

作为维恩图,我会把情况画成这样:

因此,两者之间有相当多的重叠(可能比这张图所暗示的要多),但每一个都允许至少一些其他不允许的事情。我已经给出了一个例子,说明了每个方向上的一个项目,但没有尝试详尽地列出差异。

不过,我并不完全相信整型常量表达式禁止用户定义的转换(快速测试表明,我手头的编译器允许用户定义的转换)。这将给出我最初写下这个答案时的情况,更像这样:

 类似资料:
  • 常量表达式机制是为了: 提供一种更加通用的常量表达式 允许用户自定义的类型成为常量表达式 提供了一种保证在编译期完成初始化的方法(可以在编译时期执行某些函数调用) 考虑下面这段代码: enum Flags { good=0, fail=1, bad=2, eof=4 }; constexpr int operator|(Flags f1, Flags f2) { return Flags(int(

  • 问题内容: 因此,我正在研究具有一些静态常量的此类: 然后,我想一种基于常量获取相关字符串的方法: 但是,当我编译时,在3个大小写标签的每一个上都出现错误。 我知道编译器需要在编译时就知道表达式才能编译开关,但是为什么不是常量? 问题答案: 尽管从初始化字段之后执行的任何代码的角度来看,它们是恒定的,但从JLS 的角度来看,它们不是编译时常数。有关常量表达式1的规范,请参见第15.28节常量表达式

  • 第 2 章 常量、变量和表达式 目录 1. 继续Hello World 2. 常量 3. 变量 4. 赋值 5. 表达式 6. 字符类型与字符编码

  • 问题内容: 我的转换案例声明昨天运行良好。但是,当我今天早些时候运行代码时,eclipse给我一个错误,强调了红色的case语句,并说:case表达式必须是常量表达式,它是常量,我不知道发生了什么。这是我的代码如下: 所有R.id.int都用红色下划线。 问题答案: 在常规的Android项目中,资源R类中的常量声明如下: 但是,从ADT 14开始,在图书馆项目中,它们将这样声明: 换句话说,常数

  • 问题内容: 我有以下代码 我知道根据JLS,只允许将常量表达式作为批注属性的值。但为什么?如果数据类型匹配,为什么还不够?如果要在运行时对表达式进行求值,是否有可能出错?每个规范背后都有逻辑推理吗? 问题答案: 注释就像类型扩展或有关该类型的元数据。 因为Java是一种静态类型的语言(意味着类型在编译时是已知的),所以在编译时也知道注释属性数据(元数据)似乎是合理的- 您正在定义/声明有关注释(扩

  • 整型字面值,例如42,就是常量表达式。所以,简单的数学表达式,例如,23x2-4。可以使用其来初始化const整型变量,然后将const整型变量作为新表达的一部分: const int i=23; const int two_i=i*2; const int four=4; const int forty_two=two_i-four; 使用常量表达式创建变量也可用在其他常量表达式中,有些事只能用