C++ 14プロジェクト用の軽量のstring_view
ラッパーを作成しました。MSVC2017では、コンパイル時に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
}
これは、問題を再現するために使用した コンパイラエクスプローラーリンク です。
私が知ることができることから、ポインタ値から0
を加算または減算することは常に有効です:
end()
などの実装.回避策:
end
メソッドを次のように変更すると、失敗したstatic_assert
sは成功します。
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;
に置き換えると、2つのstatic_assert
エラーが生成されます。 :-/
更新:
これは、15.7〜15.8の間に修正されたコンパイラのバグのようです。
これは、M ++のバグのように見えます。C++ 14ドラフト標準では、値_0
_をポインターに明示的に加算および減算して、 [expr.add] p7 からそれ自体と比較できます。
値0がポインター値に加算または減算されると、結果は元のポインター値と等しくなります。 2つのポインターが同じオブジェクトを指しているか、両方が同じ配列の終わりを過ぎているか、両方がヌルであり、2つのポインターが減算される場合、結果はstd :: ptrdiff_t型に変換された値0と等しくなります。
CWG欠陥1776 p0137に至り、 [expr.add] p7 を明示的に_null pointer
_と言うように調整します。
最新のドラフトでは、これがさらに明確になりました [expr.add] p4 :
整数型の式Jがポインター型の式Pに加算または減算されると、結果の型はPになります。
-PがNULLポインター値に評価され、Jが0に評価される場合、結果はNULLポインター値になります。
-それ以外の場合、Pがn個の要素を持つ配列オブジェクトxの要素x [i]を指している場合85、式P + JおよびJ + P(Jは値jを持っています)は(おそらく-仮定)0≤i+j≤nの場合は要素x [i + j]であり、式P-Jは0≤i-j≤nの場合(おそらく仮説的な)要素x [i-j]を指します。 (4.3)。
-それ以外の場合、動作は未定義です。
この変更は、編集的に this github issue および this PR を参照して行われました。
MSVCは、gccやclangと同様に、定数式にゼロを追加および減算できるという点で一貫性がありません。 定数式の未定義の動作が不正な形式 であり、診断が必要なため、これが重要です。次の場合:
_constexpr int *p = nullptr ;
constexpr int z = 0 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
_
gcc、clang、およびMSVCでは、定数式( live godbolt example )を使用できますが、悲しいことに、MSVCは次の場合にゼロ以外の値も許可するという点で二重に矛盾しています。
_constexpr int *p = nullptr ;
constexpr int z = 1 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
_
clangとgccはどちらも不正な形式であると言いますが、MSVCはそうではありません( live godbolt )。
これは間違いなく、MSVCが定数式を評価する方法のバグだと思います。GCCとClangにはコードに関する問題がなく、標準では、nullポインターに0を追加するとnullポインター([expr.add]/7)。