我研究什么是允许的核心常量表达式* ,这是覆盖在部分5.19
常量表达式中的第2款草案C ++标准它说:
一个条件表达式是一个核心常量表达式除非它涉及以下作为一个潜在的评价子表达式(3.2)中的一个,但逻辑AND(5.14),逻辑OR(5.15),和条件子表达式未评价(5.16)操作不被认为是[注:一个重载操作调用function.端注]:
并列出了在后面,包括( 重点煤矿 )子弹的排除:
- 操作将有不确定的操作 [注:包括,例如,带符号整数溢出(第5章),某些指针运算(5.7)中,由零(5.6)分裂,或某些移位操作(5.8)-end注];
咦 ? 常量表达式为什么需要这一条款涵盖未定义行为 ? 是不是有什么特别的常量表达式 ,需要不确定的行为有一种特殊的雕刻出在排除?
是否有这个条款给我们,我们也不会没有任何的优势或工具?
作为参考,这看起来像该提案的最后修订广义常量表达式 。
措辞实际上是对主题的缺陷报告#1313它说:
为常量表达式的要求目前没有,但应该排除具有不确定的行为表现,如指针运算时,指针不指向同一个数组的元素。
分辨率为目前的措词,我们现在也有,所以这显然是打算,那么什么样的工具这是否给我们?
让我们看看会发生什么,当我们试图创建一个包含未定义行为表达一个constexpr变量 ,我们将使用clang
所有下面的例子。 此代码( 见直播 ):
constexpr int x = std::numeric_limits<int>::max() + 1 ;
产生以下错误:
error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = std::numeric_limits<int>::max() + 1 ;
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: value 2147483648 is outside the range of representable values of type 'int'
constexpr int x = std::numeric_limits<int>::max() + 1 ;
^
此代码( 见直播 ):
constexpr int x = 1 << 33 ; // Assuming 32-bit int
产生这个错误:
error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = 1 << 33 ; // Assuming 32-bit int
^ ~~~~~~~
note: shift count 33 >= width of type 'int' (32 bits)
constexpr int x = 1 << 33 ; // Assuming 32-bit int
^
这个代码在constexpr函数未定义的行为:
constexpr const char *str = "Hello World" ;
constexpr char access( int index )
{
return str[index] ;
}
int main()
{
constexpr char ch = access( 20 ) ;
}
产生这个错误:
error: constexpr variable 'ch' must be initialized by a constant expression
constexpr char ch = access( 20 ) ;
^ ~~~~~~~~~~~~
note: cannot refer to element 20 of array of 12 elements in a constant expression
return str[index] ;
^
嗯,这是非常有用的编译器可以在constexpr检测未定义的行为 ,或者至少是clang
认为是不确定的 。 注意, gcc
的行为与除与左,右偏移不确定的行为的情况下, gcc
通常会产生在这些情况下警告,但仍然认为表达恒定。
我们可以通过SFINAE使用此功能来检测除了表达是否会导致溢出,下面的人为的例子灵感来自DYP的位置巧妙的回答 :
#include <iostream>
#include <limits>
template <typename T1, typename T2>
struct addIsDefined
{
template <T1 t1, T2 t2>
static constexpr bool isDefined()
{
return isDefinedHelper<t1,t2>(0) ;
}
template <T1 t1, T2 t2, decltype( t1 + t2 ) result = t1+t2>
static constexpr bool isDefinedHelper(int)
{
return true ;
}
template <T1 t1, T2 t2>
static constexpr bool isDefinedHelper(...)
{
return false ;
}
};
int main()
{
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<10,10>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<std::numeric_limits<int>::max(),1>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<unsigned int,unsigned int>::isDefined<std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()>() << std::endl ;
}
这会导致( 见直播 ):
true
false
true
该标准要求这种行为,但显然这不是明显霍华德Hinnant(欣南特)评论表明它的确是:
[...],也constexpr,意思是UB是在编译时抓
更新
不知怎的,我错过了问题的constexpr功能695编译时计算错误 ,其在旋转部分的措辞5
其中常说( 重点煤矿前进 )第4段:
如果表达式的评估过程中,其结果是没有在数学上为它的类型表示的值的范围限定或没有,该行为是未定义的, 除非当需要整数表达式中出现这样的表达(5.19 [expr.const] ),在这种情况下是形成不良的节目 。
并接着说:
打算作为一个可接受的Standardese迂回“在编译时评价”,即不直接由标准定义的概念。 目前尚不清楚这一提法充分覆盖constexpr功能。
与后注云:
[...]有想诊断在编译时间与不诊断将不实际发生在运行时错误的错误之间的张力。[...]的CWG的共识是,像1/0的表达应该简单地认为是非恒定; 任何诊断将导致从在上下文需要常量表达式使用的表达。
其中,如果我读正确的确认的意图是要能够在编译时在需要常量表达式上下文来诊断不确定的行为。
我们不能肯定地说,这是意图,但确实是强烈建议它。 在怎么区别clang
和gcc
对待不确定的变化并留下怀疑的余地。
我提出了一个constexpr左,右偏移不确定的行为不是一个错误:gcc的bug报告 。 虽然看起来这是符合,但它打破SFINAE,我们可以从我的答案看到它是一个符合标准的编译延长治疗非constexpr标准库函数为constexpr? 在执行观察的到SFINAE网友认为分歧似乎不可取委员会。
当我们谈论不确定的行为 ,但要记住,标准留下未定义这些情况下的行为是很重要的。 它不作出更有力的保护,禁止实施。 例如有些实现可以保证有符号整数溢出时回绕,而其他人可能保证饱和。
要求编译器处理涉及未定义行为将限制保证的实现可以使,他们限制生产无副作用的一些值(什么标准要求不确定的值 ),常量表达式。 这排除了很多在现实世界中找到了延长担保。
例如,一些实现方式或伴随标准(即POSIX)可以通过零限定积分分裂的行为以产生一个信号。 这是一个副作用,如果表达,在编译时计算的,而不是它会丢失。
因此,这些表达式是在编译时被拒绝,以避免副作用的执行环境损失。
还有一点,从常量表达式不包括未定义行为:常量表达式应该根据定义,可以通过在编译时,编译器进行评估。 允许一个常量表达式来调用不确定的行为让编译器本身表现出不确定的行为。 因为你编译一些邪恶的代码格式化您的硬盘驱动器编译器是不是你想要拥有的东西。