为什么常量表达式有未定义行为的排除? [英] Why do constant expressions have an exclusion for undefined behavior?

查看:226
本文介绍了为什么常量表达式有未定义行为的排除?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究核心常量表达式* 中允许的内容, 段落中的 jtc1 / sc22 / wg21 / docs / papers / 2012 / n3485.pdf>草案C ++标准,其中说:


条件表达式是一个核心常量表达式,除非它包含以下之一作为潜在求值的子表达式(3.2),但未评估的逻辑AND(5.14),逻辑OR(5.15)和条件(5.16)操作的子表达式不考虑[注意:重载操作符调用一个函数。-end note]:


并列出了以下项目中的排除项:


- 具有未定义行为的操作 [注意:包括例如有符号整数溢出(条款5),某些指针算术(5.7),除以零(5.6)或某些移位操作(5.8)-end note];


?为什么常量表达式需要此子句来涵盖未定义的行为 有这个条款给我们任何优势或工具,我们没有它没有它吗?



作为参考,这看起来像一般常数表达式

解决方案

这个词语实际上是缺陷报告#1313 ,它说:


常量表达式的要求目前不包括未定义的行为,例如当指针不指向同一数组的元素时的指针运算。




让我们看看当我们尝试创建一个 constexpr变量与包含未定义的行为的表达式,我们将使用 clang 用于所有以下示例。此代码( 查看实时 ):

  constexpr int x = std :: numeric_limits< int> :: max() 

会产生以下错误:

 错误:constexpr变量'x'必须由常量表达式初始化
constexpr int x = std :: numeric_limits< int> :: max()
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note:value 2147483648 is outside int类型的可表示值的范围
constexpr int x = std :: numeric_limits< int> :: max()+ 1;
^

此代码(查看实时 ):

  constexpr int x = 1<< 33; //假设32位int 

产生此错误:

 错误:constexpr变量'x'必须由常量表达式初始化
constexpr int x = 1< 33; //假设32位int
^ ~~~~~~~
注意:shift count 33> ='int'类型的宽度(32位)
constexpr int x = 1 << 33; //假设32位int
^

constexpr function:

  constexpr const char * str =Hello World; 

constexpr char访问(int index)
{
return str [index];
}

int main()
{
constexpr char ch = access(20);
}

产生此错误:

 错误:constexpr变量'ch'必须由常量表达式初始化
constexpr char ch = access(20);
^ ~~~~~

注意:不能引用常量表达式中12个元素的元素20
return str [index] ;
^

这很有用,编译器可以检测未定义的行为 in 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);
}

模板< T1 t1,T2 t2,decltype(t1 + t2)result = t1 + t2&
static constexpr bool isDefinedHelper(int)
{
return true;
}

模板< T1 t1,T2 t2>
静态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

标准需要此行为并不明显但显然这个 Howard Hinnant的评论表示它确实是:


[...],也是constexpr,意味着UB在编译时捕获


更新



不知何故,我错过了问题695 constexpr函数中的编译时计算错误,它围绕 5 段的措辞 表达式的求值,结果不是数学定义的,或者不在其类型的可表示值的范围内,行为是未定义的,除非这样的表达出现在需要积分常数表达式的地方(5.19 [expr.const ]),在这种情况下程序不正常


并继续说:


作为在编译时评估的可接受的标准回避,该概念不是由标准直接定义的。不清楚这个公式是否充分涵盖了constexpr的功能。


后面的注释:


$ [...]在编译时需要诊断错误,而不是诊断在运行时实际上不会发生的错误。[...] CWG认为像1/0这样的表达式应该简单地被认为是非常数; 任何诊断都会因为在需要常量表达式的上下文中使用表达式。


我正在阅读正确确认的意图是能够诊断未定义的行为在编译时需要一个常量表达式上下文。



我们不能说这是意图但是强烈建议是。 clang gcc 处理未定义的轮班的差异确实留有一些疑问。



我提交了 gcc错误报告:左右移动未定义的行为不是错误在constexpr中。虽然看起来这是符合的,它确实打破了SFINAE,我们可以从我的答案看到是一个符合的编译器扩展, constexpr标准库函数作为constexpr?在SFINAE用户观察到的实现分歧似乎不受委员会的欢迎。


I was researching what is allowed in a core constant expression*, which is covered in section 5.19 Constant expressions paragraph 2 of the draft C++ standard which says:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [ Note: An overloaded operator invokes a function.—end note ]:

and lists out the exclusions in the bullets that follows and includes (emphasis mine):

an operation that would have undefined behavior [ Note: including, for example, signed integer overflow (Clause 5), certain pointer arithmetic (5.7), division by zero (5.6), or certain shift operations (5.8) —end note ];

Huh? Why do constant expressions need this clause to cover undefined behavior? Is there something special about constant expressions that requires undefined behavior to have a special carve out in the exclusions?

Does having this clause give us any advantages or tools we would not have without it?

For reference, this looks like the last revision of the proposal for Generalized Constant Expressions.

解决方案

The wording is actually the subject of defect report #1313 which says:

The requirements for constant expressions do not currently, but should, exclude expressions that have undefined behavior, such as pointer arithmetic when the pointers do not point to elements of the same array.

The resolution being the current wording we have now, so this clearly was intended, so what tools does this give us?

Let's see what happens when we try to create a constexpr variable with an expression that contains undefined behavior, we will use clang for all the following examples. This code (see it live):

constexpr int x = std::numeric_limits<int>::max() + 1 ;

produces the following error:

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 ;
                                       ^

This code (see it live):

constexpr int x = 1 << 33 ;  // Assuming 32-bit int

produces this error:

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
                  ^

and this code which has undefined behavior in a constexpr function:

constexpr const char *str = "Hello World" ;      

constexpr char access( int index )
{
    return str[index] ;
}

int main()
{
    constexpr char ch = access( 20 ) ;
}

produces this error:

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] ;
           ^

Well that is useful the compiler can detect undefined behavior in constexpr, or at least what clang believes is undefined. Note, gcc behaves the same except in the case of undefined behavior with right and left shift, gcc will usually produce a warning in these cases but still sees the expression as constant.

We can use this functionality via SFINAE to detect whether an addition expression would cause overflow, the following contrived example was inspired by dyp's clever answer here:

#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 ;
}

which results in (see it live):

true
false
true

It is not evident that the standard requires this behavior but apparently this comment by Howard Hinnant indicates it indeed is:

[...] and is also constexpr, meaning UB is caught at compile time

Update

Somehow I missed Issue 695 Compile-time calculation errors in constexpr functions which revolves over the wording of section 5 paragraph 4 which used to say (emphasis mine going forward):

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed.

and goes on to say:

intended as an acceptable Standardese circumlocution for "evaluated at compile time," a concept that is not directly defined by the Standard. It is not clear that this formulation adequately covers constexpr functions.

and a later note says:

[...]There is a tension between wanting to diagnose errors at compile time versus not diagnosing errors that will not actually occur at runtime.[...]The consensus of the CWG was that an expression like 1/0 should simply be considered non-constant; any diagnostic would result from the use of the expression in a context requiring a constant expression.

which if I am reading correctly confirms the intention was to be able to diagnose undefined behavior at compile time in the context requiring a constant expression.

We can not definitely say this was the intent but is does strongly suggest it was. The difference in how clang and gcc treat undefined shifts does leave some room for doubt.

I filed a gcc bug report: Right and left shift undefined behavior not an error in a constexpr. Although it seems like this is conforming, it does break SFINAE and we can see from my answer to Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr? that divergence in implementation observable to SFINAE users seems undesirable to the committee.

这篇关于为什么常量表达式有未定义行为的排除?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆