我正在研究核心常量表达式*中允许的内容,这在C++标准草案的5.19 常量表达式第2段中有所描述:
条件表达式是核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式(3.2),但是未评估的逻辑AND(5.14),逻辑OR(5.15)和条件(5.16)操作的子表达式不考虑[注意:重载的运算符调用函数.-end note]:
并列出随后的子弹中的排除项并包括(强调我的):
- 具有未定义行为的操作 [注意:包括,例如,有符号整数溢出(第5条),某些指针算术(5.7),除零(5.6)或某些移位操作(5.8) - 结束注释];
嗯?为什么常量表达式需要此子句来涵盖未定义的行为?常量表达式是否有一些特殊的东西需要未定义的行为才能在排除中进行特殊划分?
拥有这个条款是否给了我们没有它的任何优势或工具?
作为参考,这看起来像广义常量表达式提案的最新修订版.
gcc 在没有警告的情况下编译以下代码:
#include <cmath>
struct foo {
static constexpr double a = std::cos(3.);
static constexpr double c = std::exp(3.);
static constexpr double d = std::log(3.);
static constexpr double e1 = std::asin(1.);
static constexpr double h = std::sqrt(.1);
static constexpr double p = std::pow(1.3,-0.75);
};
int main()
{
}
Run Code Online (Sandbox Code Playgroud)
的上面使用的标准库函数都不是constexpr功能,我们允许使用它们,其中一个常量表达式从两个需要草案C++ 11标准和草案C++ 14标准部7.1.5 [dcl.constexpr] :
[...]如果它是由构造函数调用初始化的,那么该调用应该是一个常量表达式(5.19).否则,或者如果在引用声明中使用constexpr说明符,则其初始值设定项中出现的每个完整表达式都应为常量表达式.[...]
即使使用-std=c++14 -pedantic 或-std=c++11 -pedantic没有生成警告(请参见实时).使用-fno-builtin产生错误(参见实时),表明这些标准库函数的内置版本被视为constexpr
虽然 …
请考虑以下代码:
#include <stdio.h>
constexpr int f()
{
return printf("a side effect!\n");
}
int main()
{
char a[f()];
printf("%zd\n", sizeof a);
}
Run Code Online (Sandbox Code Playgroud)
我本来期望编译器抱怨printf内部的调用f,因为f应该是constexpr,但printf不是.为什么程序编译和打印15?
int在C++ 11中左移一个负的未定义行为?
这里的相关标准段落来自5.8:
2/E1 << E2的值是E1左移E2位的位置; 空位是零填充的.如果E1具有无符号类型,则结果的值为E1×2E2,比结果类型中可表示的最大值减少一个模数.否则,如果E1具有有符号类型和非负值,并且在结果类型中可以表示E1×2E2,那么这就是结果值; 否则,行为未定义.
令我困惑的部分是:
否则,如果E1具有有符号类型和非负值,并且在结果类型中可以表示E1×2E2,那么这就是结果值; 否则,行为未定义.
这应该被解释为左移任何负数是UB吗?或者它只是意味着如果你的LS为负数且结果不适合结果类型,那么它是UB?
而且,前面的条款说:
1 /移位运算符<<和>>组从左到右.shift-expression:additive-expression shift-expression << additive-expression shift-expression >> additive-expression
操作数应为整数或无范围的枚举类型,并执行整体促销.
结果的类型是提升的左操作数的类型.如果右操作数为负数,或者大于或等于提升左操作数的位长度,则行为未定义.
这使得明确表示对其中一个操作数使用负数是UB.如果UB对另一个操作数使用负数,我希望在这里也要明确.
所以,底线是:
-1 << 1
Run Code Online (Sandbox Code Playgroud)
未定义的行为?
@Angew 提供了对Standardese的psudocode解释,它简洁地表达了一种可能(可能)有效的解释.其他人质疑这个问题是否真的与"行为未定义"的语言的适用性相比,而我们(StackOverflow)使用了"未定义行为"这一短语.这个编辑是为了提供一些我想要问的更多说明.
@Angew对Standardese的解释是:
if (typeof(E1) == unsigned integral)
value = E1 * 2^E2 % blah blah;
else if (typeof(E1) == signed integral && E1 >= 0 && representable(E1 * 2^E2))
value = E1 * 2^E2;
else
value = undefined;
Run Code Online (Sandbox Code Playgroud)
这个问题真正归结为这个 - …
我知道吸气剂总体上很糟糕,但在这里,我只是用一个来说明一个更普遍的问题.
考虑以下课程:
template <class... T>
class my_tuple final
{
private:
std::tuple<T...> _data;
public:
template <class... U>
my_tuple(U&&... u)
: _data(std::forward<U>(u)...) {}
public:
template <std::size_t I>
auto get() -> decltype(std::get<I>(_data))
{return std::get<I>(_data);}
};
Run Code Online (Sandbox Code Playgroud)
并且考虑到我不能修改这个类.
有没有办法,写一个外部元函数my_tuple_type(通过外部我的意思是一个不属于该类的元函数)来实际获取底层元组的类型?(我倾向于认为如果其中一个T...是参考是不可能的,因为只需应用std::decay或std::remove_reference返回get返回的类型也将删除原始引用).
编辑:我添加了一个构造函数来帮助测试.
EDIT2:为了澄清,我无法操作T...:我只是根据getter搜索元函数.
编辑3:从类的外部,我不知道底层元组成员的名称(这里它被命名_data,但它可能是_tuple或其他)
编辑4:作为一个例子,如果我们假设没有类型是引用/指针,这可以实现:
1)创建一个元函数,它将递归执行getter直到它失败(因此元组大小N将被知道)
2)执行一个std::decay由返回的每个类型std::get从0到N并把它们放在一起.
但如果其中一个元组元素是引用或指针,它将失败...
编辑5:我将很快发布EDIT4的实现(我正在努力)
编辑6:这不是XY问题.我试图回答的基本问题是:
考虑一个被称为概念的概念,Tuple_like其唯一条件是拥有像这里一样的模板化获取成员.问题是:从这个唯一的功能get<I>(),是否有可能提取底层元组的所有信息?
我想知道以下是否应该或不应该在 C++17 中编译
enum class E
{
A, B
};
constexpr E x = static_cast<E>(2);
Run Code Online (Sandbox Code Playgroud)
这在 Ubuntu 20.04 上使用 GCC 9.3.0 和 Clang 10.0.0 编译。
我的问题是
我认为不应该。有很多关于未定义行为 (UB) 和枚举的帖子,但我找不到任何在常量表达式上下文中提出的帖子。此外,大多数帖子使用底层类型,我认为范围枚举没有任何固定的底层类型。因为我没有获得ISO标准的副本,我将把最新的C ++发现有17草案cppreference.com这里在下面的推理。
首先,我们找到将带有 UB 的表达式从常量表达式中丢弃的段落
[expr.const]/2:表达式 e 是核心常量表达式,除非 e 的计算遵循抽象机的规则,将计算以下表达式之一:
带小节
[expr.const]/2.6 : 具有未定义行为的操作,如本国际标准的 [intro] 至 [cpp] 条款 [?注:包括,例如,有符号整数溢出(条款 [expr]),某些指针算术([expr.add]),除以零,或某些移位操作?—?尾注?];
这告诉我们常量表达式可能不包含 UB。
然后,我们找到关于从 int 到 enum 的静态转换的部分
[expr.static.cast]/10 : 整数或枚举类型的值可以显式转换为完整的枚举类型。如果原始值在枚举值 ( [dcl.enum] )的范围内,则该值不变。否则,行为是未定义的。浮点类型的值也可以显式转换为枚举类型。结果值与将原始值转换为枚举的基础类型 ([conv.fpint]) 以及随后的枚举类型相同。
这告诉我们,如果操作数在目标枚举的范围内,则静态转换的结果是明确定义的。该部分参考[decl.enum]/8关于枚举值的范围(太长无法在此处发布,我也无法正确设置格式)。无论如何,它表示非固定基础类型枚举的有效值范围由可以适合最小和最大枚举(以两个补码格式)之间的所有值的最小位集定义。
最后,在示例代码中应用这三个部分,我们可以说可以同时包含 A = 0 和 B = 1 的最小位域的大小为 …