Является ли этот неудачный тест добавлением нуля к неопределенному указателю неопределенного поведения, ошибке компилятора или чем-то еще?

Я написал упрощенную оболочку string_view для проекта c++ 14, и с 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
}

Вот ссылка Компилятора, которую я использовал для репликации проблемы.

Из того, что я могу сказать, добавление или вычитание 0 из любого значения указателя всегда справедливо:

Временное решение:

Если я изменю свой метод end на следующий, static_assert провал static_assert.

constexpr char const* end() const noexcept
{ return ( m_data == nullptr
           ? m_data
           : m_data + m_size ); }

Лужение:

Я подумал, что, возможно, выражение m_data + m_size само по себе является UB, прежде чем m_size == 0. Тем не менее, если я заменил реализацию end бессмысленным return m_data + 0; , это все равно генерирует две ошибки static_assert. : -/

Обновить:

Это похоже на ошибку компилятора, которая была установлена между 15.7 и 15.8.

Ответ 1

Это выглядит как ошибка MSVC. Стандарт проекта С++ 14 явно позволяет добавлять и вычитать значение 0 в указатель для сравнения, равный самому себе, из [expr.add] p7:

Если значение 0 добавляется или вычитается из значения указателя, результат сравнивается с исходным значением указателя. Если два указателя указывают на один и тот же объект или обе точки за концом одного и того же массива, либо оба равны нулю, а два указателя вычитаются, результат сравнивается с значением 0, преобразованным в тип std :: ptrdiff_t.

Похоже, что дефект CGG 1776 приводит к p0137, который скорректировал [expr.add] p7, чтобы явно указать null pointer.

Последний проект сделал это еще более явным [expr.add] p4:

Когда выражение J, которое имеет интегральный тип, добавляется или вычитается из выражения P типа указателя, результат имеет тип P.
- Если P оценивает значение нулевого указателя, а J оценивается как 0, результатом является значение нулевого указателя.
- В противном случае, если P указывает на элемент x [i] объекта массива x с n элементами 85, выражения P + J и J + P (где J имеет значение j) указывают на (возможно, гипотетический) элемент x [ я + j], если 0≤i + j≤n, а выражение P - J указывает на (возможно, гипотетический) элемент x [i-j], если 0≤i-j≤n. (4.3).
- В противном случае поведение не определено.

Это изменение было внесено в редакцию, чтобы увидеть эту проблему github и этот 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 позволяют это постоянное выражение (пример livebobbtt), хотя, к сожалению, MSVC вдвойне непоследовательна в том, что он также позволяет отличать ненулевое значение, учитывая следующее:

constexpr int *p = nullptr  ;
constexpr int z = 1 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;

как clang, так и gcc говорят, что он плохо сформирован, в то время как MSVC не живет (живет godbolt).

Ответ 2

Я думаю, что это определенно ошибка в том, как MSVC оценивает постоянные выражения, поскольку GCC и Clang не имеют проблем с кодом, и стандарт ясно, что добавление 0 к нулевому указателю дает нулевой указатель ([expr.add]/7).