在以下代码中(在本地和Wandbox上测试):
#include <iostream>
enum Types
{
A, B, C, D
};
void print(std::initializer_list<Types> types)
{
for (auto type : types)
{
std::cout << type << std::endl;
}
}
int main()
{
constexpr auto const group1 = { A, D };
print(group1);
return 0;
}
MSVC 15.8.5编译失败:
error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'
(全部指包含括性词
的行)
Clang 8(HEAD)报告:
error: constexpr variable 'group1' must be initialized by a constant expression
constexpr auto const group1 = { A, D };
^ ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
constexpr auto const group1 = { A, D };
^
gcc 9(负责人)报告:
In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
18 | constexpr auto const group1 = { A, D };
| ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
19 | print(group1);
| ^~~~~~
| |
| initializer_list<const Types>
为什么?
首先,它们显然都认为enum-id是非常数,尽管它们显然实际上是众所周知的编译时常数值。
其次,MSVC抱怨读取超出生存期,但group1
的生存期及其值应在print
中的整个使用过程中延长。
第三,gcc有一个奇怪的const-vs非const抱怨,我无法理解,因为初始化程序列表总是const。
最后,如果< code>constexpr被删除,除了gcc之外的所有人都将愉快地编译和运行这段代码,不会出现任何问题。虽然在这种情况下没有必要,但我看不出有什么好的理由让它不起作用。
同时,如果参数类型更改为st,gcc将仅编译和运行代码d::initializer_list
(有趣的是:gcc 8,随着参数类型的更改,确实成功编译并运行了代码,包括
contexpr
,其中gcc 9出错。
FWIW,将声明改为:
constexpr auto const group1 = std::array<Types, 2>{ A, D };
在所有三个编译器上编译和运行。因此,可能是initializer_list本身而不是枚举值行为不正常。但语法更烦人。(使用合适的
make_array
实现稍微不那么烦人,但我仍然不明白为什么原始版本是无效的。)
constexpr auto const group1 = std::array{ A, D };
也可以,这要归功于C 17模板归纳。尽管现在
print
不能接受initializer_list
;它必须基于通用容器/迭代器概念进行模板化,这很不方便。
看来< code > STD::initializer _ list 还没有(在C 17中)满足文字类型的要求(这是< code>constexpr变量的类型必须满足的要求)。
关于在C14中是否这样做的讨论可以在本文中找到:为什么std::initializer_list
没有定义为文字类型?这本身就是帖子讨论的后续内容声明constexpr initializer_list
对象合法吗?
我将C14相关文章(C14标准)中的引文与最终工作草案(C17标准)进行了比较,它们是相同的。因此,没有明确要求std::initializer_list
应该是文本类型。
引用C 17的最终工作草案(n4659):
[基本类型]/10.5
(10.5)一个可能的cv限定类类型(第12条),它具有以下所有属性:
(10.5.1)-它有一个普通的析构函数,
(10.5.2)-它要么是闭包类型(8.1.5.1),要么是聚合类型(11.6.1),或者至少有一个Constexpr构造函数或构造函数模板(可能从基类继承(10.3.3)),它不是复制或移动构造函数,
(10.5.3)-如果它是联合,它的至少一个非静态数据成员是非易失性文字类型,
(10.5.4)-如果它不是联合,它的所有非静态数据成员和基类都是非易失性文字类型。
[initializer_list.syn]/1
这就是为什么宣布一个initializer_list
对象是不合法的。
初始化std::initializer_list
时,会发生以下情况:
[已输入列表] (强调我的)
5一个对象的类型std:: initializer_列表是从一个初始化列表,如果实现生成和物化一个prvalue的类型"数组N const E",其中N是初始化列表中的元素数。该数组的每个元素被复制初始化与初始化列表的相应元素,和std:: initializer_列表对象被构造为引用该数组。[注意:为副本选择的构造函数或转换函数应在初始化列表的上下文中访问。-结束注意]如果缩小转换需要初始化任何元素,程序是病态的。[示例:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
初始化将以大致等效于此的方式实现:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
假设实现可以用一对指针构造initializer-list对象。 — 结束示例 ]
如何使用临时数组来初始化< code > STD::initializer _ list 决定了< code>initializer_list是否用常量表达式初始化。最终,根据示例(尽管是非标准的),初始化将获取数组的地址,或者它的第一个元素,这将产生一个指针类型的值。这不是有效的常量表达式。
【expr.const】(强调矿山)
5常量表达式可以是一个glvalue核心常量表达式,它指的是常量表达式(定义如下)允许的结果实体,也可以是值满足以下约束的prvalue核心常数表达式:
如果实体是具有静态存储持续时间的对象,而该对象不是临时对象,或者是值满足上述约束的临时对象,或是函数,则该实体是常量表达式的允许结果。
然而,如果数组是一个静态对象,那么初始化器将构成一个有效的常量表达式,可用于初始化< code>constexpr对象。由于< code > STD::initializer _ list 通过[dcl.init.list]/6对该临时对象具有生命期扩展的效果,所以当您将< code>group1声明为静态对象时,clang和gcc似乎也将该数组分配为静态对象,这使得初始化的格式良好性只受< code > STD::initializer _ list 是否为文字类型以及所使用的构造函数是否为< code>constexpr的影响。
最终,一切都有点模糊。
问题内容: 我有以下代码 我知道根据JLS,只允许将常量表达式作为批注属性的值。但为什么?如果数据类型匹配,为什么还不够?如果要在运行时对表达式进行求值,是否有可能出错?每个规范背后都有逻辑推理吗? 问题答案: 注释就像类型扩展或有关该类型的元数据。 因为Java是一种静态类型的语言(意味着类型在编译时是已知的),所以在编译时也知道注释属性数据(元数据)似乎是合理的- 您正在定义/声明有关注释(扩
问题内容: 在Java 5及更高版本中,您具有foreach循环,该循环可以神奇地实现任何实现的对象: 但是,仍然没有实现,这意味着要迭代一个,您必须执行以下操作: 有谁知道为什么仍然不执行? 编辑: 为澄清起见,我不是在谈论枚举的语言概念,而是在Java API中称为“ 枚举 ” 的Java特定类。 问题答案: 枚举没有被修改为支持Iterable,因为它是一个接口,而不是一个具体的类(例如Ve
问题内容: 我是一名即将毕业的计算机科学专业的学生,在我的整个编码生涯中,我发现很少使用枚举的实例,除了典型的情况(例如代表标准纸牌的面孔)外,还使用了枚举。 您是否知道在日常编码中使用枚举的任何巧妙方法? 为什么枚举如此重要,在什么情况下应该能够确定建立枚举是最佳方法? 问题答案: 这些是主要的论点,以及短的例子。 的情况 从Java 6开始,是一个凌乱类的示例,该类可以从使用中受益匪浅(除
我听到一些人建议在C++中使用枚举类,因为它们的类型安全。 但这到底意味着什么呢?
我想将枚举的所有可用值添加到消息中。但是我不能,因为它需要一个常量表达式。 结果错误: 如何做到这一点?
问题内容: 我想知道为什么在Java语言中a 不能扩展。 我不是在谈论一个延伸的(这不能做,因为Java没有多重继承,而Š隐含延长),但一类的以只添加额外的方法,而不是额外的枚举值。 就像是: 要这样使用: 因此,有人可以对此限制提供理由(或将我指向正确的JLS部分)吗? 问题答案: 我认为 他们这样做 的答案来自以下问题: 在您的示例中,如何实例化MyClass?用户永远不会(通过)显式实例化枚