我为C ++ 14项目编写了一个轻量级的string_view
包装器,在MSVC 2017中,它在编译时触发了static_assert
,但在运行时传递了相同的代码常规assert
。我的问题是,这是编译器错误,明显的未定义行为还是其他所有原因?
以下是摘要代码:
#include <cassert> // assert
#include <cstddef> // size_t
class String_View
{
char const* m_data;
std::size_t m_size;
public:
constexpr String_View()
: m_data( nullptr ),
m_size( 0u )
{}
constexpr char const* begin() const noexcept
{ return m_data; }
constexpr char const* end() const noexcept
{ return m_data + m_size; }
};
void static_foo()
{
constexpr String_View sv;
// static_assert( sv.begin() == sv.end() ); // this errors
static_assert( sv.begin() == nullptr );
// static_assert( sv.end() == nullptr ); // this errors
}
void dynamic_foo()
{
String_View const sv;
assert( sv.begin() == sv.end() ); // this compiles & is optimized away
assert( sv.begin() == nullptr );
assert( sv.end() == nullptr ); // this compiles & is optimized away
}
这是我用来复制问题的Compiler Explorer link。
据我所知,从任何指针值中添加或减去0
始终是有效的:
end()
的实现等。解决方法:
如果我将end
方法更改为以下方法,则失败的static_assert
将通过。
constexpr char const* end() const noexcept
{ return ( m_data == nullptr
? m_data
: m_data + m_size ); }
修补:
在评估m_data + m_size
之前,我认为表达式m_size == 0
本身就是UB。但是,如果我用荒谬的end
代替return m_data + 0;
的实现,仍然会产生两个static_assert
错误。 :-/
更新:
这似乎是一个已在15.7和15.8之间修复的编译器错误。
答案 0 :(得分:13)
这看起来像是MSVC错误,C ++ 14草案标准明确允许从[expr.add]p7到指针的值进行加法和减法,以将其与CWG defect 1776进行比较:
如果将值0添加到指针值或从指针值中减去,结果将等于原始指针值。如果两个指针指向同一对象,或者两个指针都指向同一数组的末尾,或者两个指针都为空,并且两个指针相减,则结果等于等于转换为std :: ptrdiff_t类型的值0。 >
似乎[expr.add]p7导致p0137调整了[expr.add]p4以明确说出0
。
最新草案使这一点更加明确this github issue:
将具有整数类型的表达式J添加到指针类型的表达式P或从中减去时,结果的类型为P。
-如果P的计算结果为空指针值,而J的计算结果为0,则结果为空指针值。
-否则,如果P指向具有n个元素的数组对象x的元素x [i],85则表达式P + J和J + P(其中J的值为j)指向元素x [(可能是假设的)]。 i + j]如果0≤i+j≤n,并且表达式P-J指向(可能是假设的)元素x [ij](如果0≤i-j≤n)。 (4.3)。
-否则,行为是不确定的。
对此更改进行了编辑,请参见this PR和undefined behavior in a constant expression is ill-formed。
MSVC在这里是不一致的,因为它允许像gcc和clang一样在常量表达式中添加和减去零。这很关键,因为live godbolt example因此需要诊断。给出以下内容:
null pointer
gcc,clang和MSVC允许它使用常量表达式(live godbolt),尽管遗憾的是,MSVC在允许以下值方面也不一致,因为它也允许非零值:
constexpr int *p = nullptr ;
constexpr int z = 0 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
clang和gcc都说它格式错误,而MSVC却没有({{3}})。
答案 1 :(得分:12)
我认为这绝对是MSVC评估常量表达式的方式中的一个错误,因为GCC和Clang的代码没有问题,并且标准很明显,将0赋给null指针会产生null指针([expr。添加] / 7)。